Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: Deploy Docs

on:
push:
branches:
- main
workflow_dispatch:

permissions:
contents: read
pages: write
id-token: write

concurrency:
group: pages
cancel-in-progress: false

jobs:
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: astral-sh/setup-uv@v4

- name: Build docs
run: uv run --group docs mkdocs build

- uses: actions/upload-pages-artifact@v3
with:
path: site/

- uses: actions/deploy-pages@v4
id: deployment
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ build/
.coverage
htmlcov/

# Docs
site/

# IDE
.vscode/
.idea/
Expand Down
8 changes: 7 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.PHONY: build publish publish-test test clean
.PHONY: build publish publish-test test clean docs docs-serve

build: clean
uv build
Expand All @@ -14,3 +14,9 @@ test:

clean:
rm -rf dist/

docs:
uv run --group docs mkdocs build

docs-serve:
uv run --group docs mkdocs serve
54 changes: 27 additions & 27 deletions chatwoot/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,33 @@

A modern, type-safe Python SDK for the Chatwoot API.

Example:
>>> from chatwoot import ChatwootClient
>>>
>>> client = ChatwootClient(
... base_url="https://app.chatwoot.com",
... api_token="your_api_token_here"
... )
>>>
>>> # Fetch your profile
>>> profile = client.profile.get()
>>> print(f"Logged in as: {profile.name}")
>>>
>>> # List open conversations
>>> conversations = client.conversations.list(
... account_id=1,
... status="open"
... )
>>> print(f"Open conversations: {len(conversations)}")
>>>
>>> # Send a message
>>> message = client.messages.create(
... account_id=1,
... conversation_id=42,
... content="Hello from the Python SDK!"
... )
>>>
>>> client.close()
Examples:
from chatwoot import ChatwootClient

client = ChatwootClient(
base_url="https://app.chatwoot.com",
api_token="your_api_token_here"
)

# Fetch your profile
profile = client.profile.get()
print(f"Logged in as: {profile.name}")

# List open conversations
conversations = client.conversations.list(
account_id=1,
status="open"
)
print(f"Open conversations: {len(conversations)}")

# Send a message
message = client.messages.create(
account_id=1,
conversation_id=42,
content="Hello from the Python SDK!"
)

client.close()
"""

from chatwoot.client import AsyncChatwootClient, ChatwootClient
Expand Down
20 changes: 10 additions & 10 deletions chatwoot/_http.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,21 +36,21 @@ def _unwrap_response(self, data: dict[str, Any]) -> dict[str, Any] | list[Any]:
"""Unwrap API response to extract actual data.

Chatwoot API returns data in various formats:
- {payload: [...]} - list response
- {data: {payload: [...]}} - nested list response
- {id: 1, ...} - single object response
- {payload: []} - list response
- {data: {payload: []}} - nested list response
- {id: 1, } - single object response
"""
if not isinstance(data, dict):
return data

# {data: {payload: [...]}} or {data: ...}
# {data: {payload: []}} or {data: }
if "data" in data:
nested_data = data["data"]
if isinstance(nested_data, dict) and "payload" in nested_data:
return nested_data["payload"]
return nested_data

# {payload: ...}
# {payload: }
if "payload" in data:
return data["payload"]

Expand Down Expand Up @@ -206,21 +206,21 @@ def _unwrap_response(self, data: dict[str, Any]) -> dict[str, Any] | list[Any]:
"""Unwrap API response to extract actual data.

Chatwoot API returns data in various formats:
- {payload: [...]} - list response
- {data: {payload: [...]}} - nested list response
- {id: 1, ...} - single object response
- {payload: []} - list response
- {data: {payload: []}} - nested list response
- {id: 1, } - single object response
"""
if not isinstance(data, dict):
return data

# {data: {payload: [...]}} or {data: ...}
# {data: {payload: []}} or {data: }
if "data" in data:
nested_data = data["data"]
if isinstance(nested_data, dict) and "payload" in nested_data:
return nested_data["payload"]
return nested_data

# {payload: ...}
# {payload: }
if "payload" in data:
return data["payload"]

Expand Down
96 changes: 48 additions & 48 deletions chatwoot/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,29 +19,29 @@ class ChatwootClient:
This is the main entry point for interacting with the Chatwoot API.
All API operations are organized into resource-specific namespaces.

Example:
>>> client = ChatwootClient(
... base_url="https://app.chatwoot.com",
... api_token="your_api_token_here"
... )
>>>
>>> # Fetch user profile
>>> profile = client.profile.get()
>>> print(profile.name)
>>>
>>> # List conversations
>>> conversations = client.conversations.list(account_id=1, status="open")
>>>
>>> # Send a message
>>> message = client.messages.create(
... account_id=1,
... conversation_id=42,
... content="Hello from SDK!"
... )
>>>
>>> # Use as context manager for automatic cleanup
>>> with ChatwootClient(base_url="...", api_token="...") as client:
... profile = client.profile.get()
Examples:
client = ChatwootClient(
base_url="https://app.chatwoot.com",
api_token="your_api_token_here"
)

# Fetch user profile
profile = client.profile.get()
print(profile.name)

# List conversations
conversations = client.conversations.list(account_id=1, status="open")

# Send a message
message = client.messages.create(
account_id=1,
conversation_id=42,
content="Hello from SDK!"
)

# Use as context manager for automatic cleanup
with ChatwootClient(base_url="", api_token="") as client:
profile = client.profile.get()
"""

def __init__(self, base_url: str, api_token: str, timeout: float = 30.0):
Expand Down Expand Up @@ -90,31 +90,31 @@ class AsyncChatwootClient:
This is the async version of the Chatwoot client, using async/await
patterns for non-blocking I/O operations.

Example:
>>> import asyncio
>>>
>>> async def main():
... client = AsyncChatwootClient(
... base_url="https://app.chatwoot.com",
... api_token="your_api_token_here"
... )
...
... # Fetch user profile
... profile = await client.profile.get()
... print(profile.name)
...
... # List conversations
... conversations = await client.conversations.list(
... account_id=1,
... status="open"
... )
...
... await client.aclose()
>>>
>>> # Or use as async context manager
>>> async def main():
... async with AsyncChatwootClient(base_url="...", api_token="...") as client:
... profile = await client.profile.get()
Examples:
import asyncio

async def main():
client = AsyncChatwootClient(
base_url="https://app.chatwoot.com",
api_token="your_api_token_here"
)

# Fetch user profile
profile = await client.profile.get()
print(profile.name)

# List conversations
conversations = await client.conversations.list(
account_id=1,
status="open"
)

await client.aclose()

# Or use as async context manager
async def main():
async with AsyncChatwootClient(base_url="", api_token="") as client:
profile = await client.profile.get()
"""

def __init__(self, base_url: str, api_token: str, timeout: float = 30.0):
Expand Down
46 changes: 23 additions & 23 deletions chatwoot/resources/agents.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ def list(self, account_id: int) -> list[Agent]:
ChatwootAuthError: If authentication fails
ChatwootPermissionError: If user doesn't have access

Example:
>>> agents = client.agents.list(account_id=1)
>>> for agent in agents:
... print(agent.name, agent.role)
Examples:
agents = client.agents.list(account_id=1)
for agent in agents:
print(agent.name, agent.role)
"""
response = self._http.get(f"/api/v1/accounts/{account_id}/agents")
if isinstance(response, list):
Expand All @@ -48,9 +48,9 @@ def get(self, account_id: int, agent_id: int) -> Agent:
ChatwootNotFoundError: If agent not found
ChatwootAuthError: If authentication fails

Example:
>>> agent = client.agents.get(account_id=1, agent_id=10)
>>> print(agent.email)
Examples:
agent = client.agents.get(account_id=1, agent_id=10)
print(agent.email)
"""
response = self._http.get(f"/api/v1/accounts/{account_id}/agents/{agent_id}")
return Agent(**response)
Expand Down Expand Up @@ -79,13 +79,13 @@ def add(
ChatwootValidationError: If validation fails
ChatwootAuthError: If authentication fails

Example:
>>> agent = client.agents.add(
... account_id=1,
... name="John Doe",
... email="john@example.com",
... role="agent"
... )
Examples:
agent = client.agents.add(
account_id=1,
name="John Doe",
email="john@example.com",
role="agent"
)
"""
data = {"name": name, "email": email, "role": role, **kwargs}
response = self._http.post(f"/api/v1/accounts/{account_id}/agents", json=data)
Expand All @@ -111,13 +111,13 @@ def update(
ChatwootNotFoundError: If agent not found
ChatwootValidationError: If validation fails

Example:
>>> agent = client.agents.update(
... account_id=1,
... agent_id=10,
... name="Jane Doe",
... role="administrator"
... )
Examples:
agent = client.agents.update(
account_id=1,
agent_id=10,
name="Jane Doe",
role="administrator"
)
"""
response = self._http.patch(
f"/api/v1/accounts/{account_id}/agents/{agent_id}",
Expand All @@ -136,8 +136,8 @@ def remove(self, account_id: int, agent_id: int) -> None:
ChatwootNotFoundError: If agent not found
ChatwootPermissionError: If user doesn't have permission

Example:
>>> client.agents.remove(account_id=1, agent_id=10)
Examples:
client.agents.remove(account_id=1, agent_id=10)
"""
self._http.delete(f"/api/v1/accounts/{account_id}/agents/{agent_id}")

Expand Down
Loading