Python SDK for Dooray.com with Socket Mode support
Project description
Dooray SDK - Python
A comprehensive Python SDK for building agents and bots on Dooray.com. This document covers the core classes, decorators, and usage patterns.
Installation
Install via pip
# Currently in alpha (requires --pre flag)
pip install --pre dooray-sdk
# Or specify version explicitly
pip install dooray-sdk==0.0.1a2
Install with aiohttp (Recommended)
pip install --pre "dooray-sdk[aiohttp]"
# Or specify version explicitly
pip install "dooray-sdk[aiohttp]==0.0.1a2"
Using Virtual Environment (Recommended)
# Create virtual environment
python3 -m venv .venv
# Activate (Linux/macOS)
source .venv/bin/activate
# Activate (Windows)
.venv\Scripts\activate
# Install
pip install --pre "dooray-sdk[aiohttp]"
Dependencies
| Package | Version | Description |
|---|---|---|
aiohttp |
>= 3.8.0 | Async HTTP/WebSocket client |
requests |
>= 2.28.0 | Sync HTTP client |
Quick Start (Templates)
Use the CLI tool to quickly bootstrap your project.
Install with Templates
pip install --pre "dooray-sdk[templates]"
Usage
# List available templates
dooray-sdk list
# Create an echo bot project
dooray-sdk init echo-bot
# Create with default values (no prompts)
dooray-sdk init echo-bot --no-input
# Create in a specific directory
dooray-sdk init echo-bot -o ./projects
Available Templates
| Template | Description |
|---|---|
echo-bot |
A simple bot that echoes messages back |
Environment Variables
The SDK uses the following environment variables:
| Variable | Required | Description | Example |
|---|---|---|---|
DOORAY_AGENT_TOKEN |
Yes | Agent authentication token | your_agent_token |
DOORAY_DOMAIN |
Yes | Dooray domain | company.dooray.com |
Classes
Agent
The core class for building Dooray agents. Supports message handling, auto-replies, and event processing.
class Agent:
def __init__(
self,
token: str = None,
domain: str = None,
services: List[str] = None,
**kwargs
)
Constructor Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
token |
str | None |
None |
Agent token (falls back to DOORAY_AGENT_TOKEN env var) |
domain |
str | None |
None |
Dooray domain (falls back to DOORAY_DOMAIN env var) |
services |
List[str] | None |
["messenger"] |
List of services to connect |
Properties
| Property | Type | Description |
|---|---|---|
token |
str |
Agent authentication token |
domain |
str |
Dooray domain |
services |
List[str] |
Connected service list |
base_url |
str |
API base URL |
is_connected |
bool |
Connection status |
Methods
| Method | Description |
|---|---|
run() |
Start the agent (blocking) |
on(event_type, action, service) |
Event handler decorator |
add_handler(event_type, handler, action, service) |
Register handler (non-decorator) |
reply_to(trigger, response) |
Simple response mapping |
send_message(channel, text) |
Send a message |
Example - Basic Usage
from dooray_sdk import Agent
agent = Agent()
@agent.messenger
def handle_message(req):
"""Handle messenger messages."""
if req.text:
req.reply(f"Received: {req.text}")
agent.run()
Example - Multi-Service
from dooray_sdk import Agent
agent = Agent(services=["messenger", "task", "wiki"])
@agent.messenger
def handle_messenger(req):
req.reply("This is a messenger message")
@agent.task
def handle_task(req):
print(f"Task event: {req.action}")
@agent.wiki
def handle_wiki(req):
print(f"Wiki event: {req.action}")
agent.run()
SocketModeRequest
Represents an incoming request received via WebSocket.
@dataclass
class SocketModeRequest:
envelope_id: str
type: str
payload: Dict[str, Any]
service: str = ""
action: str = ""
entity: EntityWrapper = None
actor: ActorWrapper = None
action_data: ActionWrapper = None
Properties
| Property | Type | Description |
|---|---|---|
envelope_id |
str |
Unique message ID |
type |
str |
Event type (message, task, page, etc.) |
payload |
Dict[str, Any] |
Raw payload data |
service |
str |
Service name (messenger, task, wiki) |
action |
str |
Action type (create, update, delete) |
entity |
EntityWrapper |
Entity information wrapper |
actor |
ActorWrapper |
Actor information wrapper |
action_data |
ActionWrapper |
Action data wrapper |
Convenience Properties
| Property | Type | Description |
|---|---|---|
is_message |
bool |
Whether this is a message type |
text |
str | None |
Message text |
channel |
str | None |
Channel ID |
data |
Dict[str, Any] |
Raw data (alias for payload) |
Methods
| Method | Description |
|---|---|
reply(text) |
Send sync/async response |
is_type(types) |
Check type |
is_action(actions) |
Check action |
is_service(services) |
Check service |
to_dict() |
Convert to dictionary |
from_dict(data, default_service) |
Create from dictionary (class method) |
Example - Request Handling
@agent.messenger
def handle(req):
# Check message type
if req.is_message:
print(f"Text: {req.text}")
print(f"Channel: {req.channel}")
# Check action
if req.is_action("create"):
print("This is a new message")
# Check service
if req.is_service("messenger"):
req.reply("Responding from messenger")
WebClient
Synchronous REST API client.
class WebClient:
def __init__(
self,
token: str,
base_url: str = "https://api.dooray.com/",
timeout: int = 30
)
Methods
| Method | Return Type | Description |
|---|---|---|
send_message(channel, text, **kwargs) |
Dict |
Send a message |
get_member(member_id) |
Dict |
Get organization member info |
AsyncWebClient
Asynchronous REST API client.
class AsyncWebClient:
def __init__(
self,
token: str,
base_url: str = "https://api.dooray.com/"
)
Methods
| Method | Return Type | Description |
|---|---|---|
send_message(channel, text, **kwargs) |
Dict |
Send a message |
get_member(member_id) |
Dict |
Get organization member info |
Decorators
Service Decorators
Register event handlers for each service.
@agent.messenger # All messenger service events
@agent.task # All task service events
@agent.wiki # All wiki service events
@agent.on() Decorator
Unified event handler decorator.
def on(
event_type: Union[str, List[str]] = "all",
action: Union[str, List[str]] = None,
service: Union[str, List[str]] = None
) -> Callable
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
event_type |
str | List[str] |
"all" |
Event type filter |
action |
str | List[str] | None |
None |
Action filter |
service |
str | List[str] | None |
None |
Service filter |
Examples
# Handle only message events
@agent.on("message")
def handle_message(req):
pass
# Handle specific action
@agent.on("message", action="create")
def handle_new_message(req):
pass
# Handle multiple types
@agent.on(["task", "page"])
def handle_task_or_page(req):
pass
# Combine service and action
@agent.on(service="wiki", action=["create", "update"])
def handle_wiki_changes(req):
pass
Request Data Structure
The SDK provides wrapper types that enable dot notation access to dictionary data.
Type Structure Overview
SocketModeRequest
├── entity: EntityWrapper # Entity info (task, page, etc.)
│ ├── type: str # Entity type
│ └── data: DataWrapper # Entity data
├── actor: ActorWrapper # Action performer info
│ ├── type: str # Actor type
│ └── data: DataWrapper # Actor data
└── action_data: ActionWrapper # Action details
├── type: str # Action type
└── data: DataWrapper # Action data
DataWrapper
The base class for all wrapper types. Enables dot notation access to dictionary data.
class DataWrapper:
def __getattr__(self, name: str) -> Any # Dot notation access
def __getitem__(self, key: str) -> Any # Dictionary access
def get(self, key: str, default=None) # Safe access
def to_dict(self) -> Dict[str, Any] # Return original dictionary
Usage:
# Dot notation access
data.id
data.subject
data.nested.value
# Dictionary access
data["id"]
data.get("subject", "default")
# Get original dictionary
data.to_dict()
EntityWrapper
Wraps entity information (tasks, pages, etc.).
| Property | Type | Description |
|---|---|---|
type |
str |
Entity type (e.g., "task", "page") |
data |
DataWrapper |
Entity detail data |
Usage:
@agent.on("task")
def handle_task(req):
print(req.entity.type) # "task"
print(req.entity.data.id) # Task ID
print(req.entity.data.subject) # Task subject
Message Data
For messenger messages, entity.type is "message".
| Property | Type | Description |
|---|---|---|
id |
str |
Message ID |
channelId |
str |
Channel ID |
senderId |
str |
Sender member ID |
text |
str |
Message text |
sentAt |
int |
Sent time (timestamp ms) |
seq |
int |
Message sequence number |
directMemberId |
str |
DM recipient member ID |
parentChannelId |
str |
Parent channel ID (threads) |
@agent.messenger
def handle(req):
print(req.entity.message.text)
print(req.entity.message.channelId)
print(req.entity.message.senderId)
print(req.entity.message.sentAt)
ActorWrapper
Wraps information about the user who performed the action.
| Property | Type | Description |
|---|---|---|
type |
str |
Actor type (e.g., "organizationMember") |
data |
DataWrapper |
Actor detail data |
organizationMember |
LazyMemberData | None |
Organization member info (supports lazy loading) |
Usage:
@agent.on("message")
def handle(req):
if req.actor:
print(req.actor.type) # "organizationMember"
# Access via organizationMember (recommended)
member = req.actor.organizationMember
print(member.id) # Member ID (local data)
print(member.name) # Member name (lazy loading)
organizationMember Fields
| Property | Type | Description |
|---|---|---|
id |
str |
Member ID |
name |
str |
Member name |
externalEmailAddress |
str |
Email address |
nickname |
str |
Nickname |
englishName |
str |
English name |
nativeName |
str |
Native name |
userCode |
str |
User code |
locale |
str |
Locale |
timezoneName |
str |
Timezone |
Lazy Loading
organizationMember returns data included in the WebSocket message immediately, and fetches missing fields via API when accessed.
@agent.messenger
async def handle(req):
member = req.actor.organizationMember
# Local data (no API call)
member_id = member.id
# Async access - API call when needed
name = await member.name
email = await member.externalEmailAddress
# Sync access (print, comparison, etc.)
print(member.name)
if member.name == "John Doe":
await req.reply("Hello!")
# Get full data
full_data = await member
ActionWrapper
Wraps action detail information.
| Property | Type | Description |
|---|---|---|
type |
str |
Action type (e.g., "create", "update") |
data |
DataWrapper |
Action detail data |
Usage Examples
Echo Agent
from dooray_sdk import Agent
agent = Agent()
@agent.messenger
def echo(req):
if req.text:
req.reply(f"Echo: {req.text}")
agent.run()
Auto-Reply Agent
from dooray_sdk import Agent
agent = Agent()
# Simple mapping
agent.reply_to("ping", "pong")
agent.reply_to("hello", "Hello there!")
# Custom handler
@agent.messenger
def handle(req):
text = req.text or ""
if text.startswith("help"):
req.reply("Available commands: ping, hello, help")
agent.run()
Multi-Service Agent
from dooray_sdk import Agent
agent = Agent(services=["messenger", "task", "wiki"])
@agent.messenger
def on_message(req):
print(f"[Messenger] {req.text}")
req.reply("Message received!")
@agent.task
def on_task(req):
print(f"[Task] {req.action}: {req.entity.data.subject}")
@agent.wiki
def on_wiki(req):
print(f"[Wiki] {req.action}: {req.entity.data.title}")
@agent.on("all")
def on_any(req):
print(f"[All] {req.service}/{req.type}/{req.action}")
agent.run()
Error Handling
Common Exceptions
| Exception | Cause | Solution |
|---|---|---|
ValueError |
Environment variables not set | Set DOORAY_AGENT_TOKEN and DOORAY_DOMAIN |
ConnectionError |
WebSocket connection failed | Check network and token |
RuntimeError |
Calling reply before agent starts | Use after agent.run() |
Exception Handling Example
from dooray_sdk import Agent
try:
agent = Agent()
except ValueError as e:
print(f"Configuration error: {e}")
print("Please set DOORAY_AGENT_TOKEN and DOORAY_DOMAIN")
exit(1)
@agent.messenger
def handle(req):
try:
# Business logic
process_message(req)
except Exception as e:
# Log error (avoid exposing errors to users)
logger.error(f"Processing error: {e}")
agent.run()
License
MIT License
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file dooray_sdk-0.0.1a2.tar.gz.
File metadata
- Download URL: dooray_sdk-0.0.1a2.tar.gz
- Upload date:
- Size: 62.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
49995569867d6bf49e1e27f6bec9149670f6b1408bd552cdf4fb5c7420965803
|
|
| MD5 |
f7d035de1ca151636adc406f973da67b
|
|
| BLAKE2b-256 |
4e6422f2b8d2f6c33fb84b49f7884aa1c323b4b298c03e5aaac1bcafcc354bb8
|
File details
Details for the file dooray_sdk-0.0.1a2-py3-none-any.whl.
File metadata
- Download URL: dooray_sdk-0.0.1a2-py3-none-any.whl
- Upload date:
- Size: 48.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
62d07f7eb6055310a99dd3142e50bbf79f7210586702de636892b1928e685522
|
|
| MD5 |
15400ca45711560b0cc6d9d768d83135
|
|
| BLAKE2b-256 |
1bd43bd4dd29b0c6ea173974b070eef1a3a99a18391a909cb908a2f603f89c48
|