asyncio native asterisk client for python
Project description
ARI Client Library
A Python client library for Asterisk REST Interface (ARI) that provides an object-oriented approach to managing channels, bridges, and events.
Architecture
The library follows a clean architecture pattern with separation of concerns:
- AriClient: Main client class that handles WebSocket connections and event dispatching
- AriClientController: Separate controller class that handles all HTTP API operations
- Model Objects: Bridge, Channel, and Event objects that encapsulate state and provide methods for actions
Key Design Principles
- All actions are performed via Bridge, Channel, and Event objects - This ensures that operations are context-aware and type-safe
- Controller is separate from client - The controller handles HTTP operations, while the client manages WebSocket connections
- Objects are self-contained - Each Bridge, Channel, and Event object has its own controller reference for performing actions
Installation
pip install -r requirements.txt
# or using uv
uv sync
Quick Start
import asyncio
from ari_client import AriClient, StasisStartEvent, StasisEndEvent, ChannelDtmfReceivedEvent
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# Create client
client = AriClient(
host="localhost",
port=8088,
ari_user="asterisk",
ari_password="asterisk",
tls_enabled=False
)
# Define event handlers
@client.on_stasis_start
async def on_stasis_start(event: StasisStartEvent):
logger.info(f"Channel entered Stasis: {event.channel.id}")
# Answer the channel using the channel object
await event.channel.answer()
# Create a bridge using the controller
bridge = await client.ari.create_bridge(type="mixing")
# Add channel to bridge using the bridge object
await bridge.add_channel(event.channel.id)
# Create external media using the controller
external_media = await client.ari.create_external_media(
external_host="192.168.1.100:10000",
format="ulaw"
)
# Add external media to bridge
await bridge.add_channel(external_media.id)
await external_media.answer()
@client.on_stasis_end
async def on_stasis_end(event: StasisEndEvent):
logger.info(f"Channel left Stasis: {event.channel.id}")
@client.on_channel_dtmf_received
async def on_dtmf(event: ChannelDtmfReceivedEvent):
logger.info(f"DTMF received: digit={event.digit} on channel {event.channel.id}")
# Main function
async def main():
await client.connect(app="myapp", subscribe_to_all=True)
# Originate a call
channel = await client.ari.originate(
endpoint="PJSIP/1001",
timeout=30
)
logger.info(f"Originated channel: {channel.id}")
# Keep running
try:
await asyncio.sleep(3600)
except KeyboardInterrupt:
logger.info("Shutting down...")
finally:
await client.disconnect()
if __name__ == "__main__":
asyncio.run(main())
Core Concepts
Event Objects
Event objects (StasisStartEvent, StasisEndEvent, ChannelDtmfReceivedEvent) are received when channels enter/leave your Stasis application or when DTMF digits are received. They contain channel information and can be used to access the channel object for performing actions.
Note: To create bridges, external media, or originate calls, use the controller via client.ari rather than event methods.
Channel Objects
Channel objects represent Asterisk channels and provide methods for channel operations:
channel.answer()- Answer the channelchannel.stop()- Hang up the channelchannel.dial()- Dial the channelchannel.record(name, format, ...)- Record audio from the channelchannel.snoop(...)- Spy/whisper on the channelchannel.send_dtmf(dtmf, ...)- Send DTMF tones to the channelchannel.redirect(endpoint)- Redirect the channel to a different endpointchannel.move(app, ...)- Move the channel to another Stasis application
Bridge Objects
Bridge objects represent Asterisk bridges and provide methods for bridge operations:
bridge.add_channel(channel_id)- Add a channel to the bridgebridge.stop()- Destroy the bridge
API Reference
AriClient
Main client class for connecting to Asterisk ARI.
Constructor
AriClient(
host: str,
port: int,
ari_user: str,
ari_password: str,
tls_enabled: bool = False
)
Methods
async connect(app: str, subscribe_to_all: bool = False)- Connect to Asterisk and start listening for eventson_stasis_start(handler)- Register handler for StasisStart events (can be used as decorator)on_stasis_end(handler)- Register handler for StasisEnd events (can be used as decorator)on_channel_dtmf_received(handler)- Register handler for ChannelDtmfReceived events (can be used as decorator)ari- Get the ari controller instance for performing actions outside event handlersasync disconnect()- Disconnect from Asterisk
Event Handlers
Event handlers can be registered using decorators or method calls:
# As decorator
@client.on_stasis_start
async def handler(event: StasisStartEvent):
pass
@client.on_channel_dtmf_received
async def on_dtmf(event: ChannelDtmfReceivedEvent):
print(f"Got digit: {event.digit}")
# As method call
async def handler(event: StasisStartEvent):
pass
client.on_stasis_start(handler)
AriClientController
Controller class that handles all HTTP API operations. Typically accessed via client.ari or through event/channel/bridge objects.
Methods
async answer_channel(channel_id: str)- Answer a channelasync stop_channel(channel_id: str)- Hang up a channelasync create_bridge(type: str, bridge_id: Optional[str] = None, name: Optional[str] = None) -> Bridge- Create a bridgeasync bridge_add_channel(bridge_id: str, channel_id: str)- Add channel to bridgeasync stop_bridge(bridge_id: str)- Destroy a bridgeasync create_external_media(...) -> Channel- Create external media channelasync originate(...) -> Channel- Originate a new channelasync send_dtmf(channel_id, dtmf, before, between, duration, after)- Send DTMF to a channelasync redirect_channel(channel_id, endpoint)- Redirect a channel to a different endpointasync move_channel(channel_id, app, app_args)- Move a channel to another Stasis application
Event
Base event class for all ARI events.
StasisStartEvent
Event received when a channel enters your Stasis application.
Properties
type: EventType- Event type (STASIS_START)timestamp: datetime- Event timestampargs: List[str]- Arguments passed to the Stasis applicationchannel: Channel- The channel that entered Stasisasterisk_id: str- Asterisk instance IDapplication: str- Application name
StasisEndEvent
Event received when a channel leaves your Stasis application.
Properties
type: EventType- Event type (STASIS_END)timestamp: datetime- Event timestampchannel: Channel- The channel that left Stasisapplication: str- Application name
ChannelDtmfReceivedEvent
Event received when a DTMF digit is received on a channel.
Properties
type: EventType- Event type (CHANNEL_DTMF_RECEIVED)timestamp: datetime- Event timestampdigit: str- DTMF digit received (0-9, A-D, *, #)duration_ms: int- Duration of the DTMF digit in millisecondschannel: Channel- The channel on which DTMF was receivedasterisk_id: str- Asterisk instance IDapplication: str- Application name
Channel
Represents an Asterisk channel.
Properties
id: str- Channel unique identifiername: str- Channel namestate: str- Channel statecaller: CallerID- Caller informationconnected: CallerID- Connected party informationcreationtime: datetime- Channel creation timestamp
Methods
async answer()- Answer the channelasync stop()- Hang up the channelasync dial(caller, timeout)- Dial the channelasync record(name, format, ...)- Record audio from the channelasync snoop(spy, whisper, ...)- Spy/whisper on the channelasync send_dtmf(dtmf, before, between, duration, after)- Send DTMF tones to the channelasync redirect(endpoint)- Redirect the channel to a different endpointasync move(app, app_args)- Move the channel to another Stasis application
Bridge
Represents an Asterisk bridge.
Properties
id: str- Bridge unique identifierbridge_type: BridgeType- Type of bridge (MIXING, HOLDING)name: str- Bridge namechannels: List[str]- List of channel IDs in the bridgevideo_mode: Optional[VideoMode]- Video mode if applicable
Methods
async add_channel(channel_id: str)- Add a channel to the bridgeasync stop()- Destroy the bridge
Best Practices
- Always use event/channel/bridge objects for actions - This ensures proper context and type safety
- Handle exceptions in event handlers - The library automatically logs exceptions, but you should handle them appropriately
- Use the controller for operations - Access the controller via
client.arito create bridges, external media, or originate calls - Store bridge/channel references - If you need to reference bridges or channels later, store them in a dictionary or similar structure
Example: Call Bridging
bridge_map: dict[str, Bridge] = {}
@client.on_stasis_start
async def on_stasis_start(event: StasisStartEvent):
# Skip external media channels
if event.channel.name.startswith("UnicastRTP"):
return
# Answer the incoming channel
await event.channel.answer()
# Create a mixing bridge using the controller
bridge = await client.ari.create_bridge(type="mixing,proxy_media")
# Add the channel to the bridge
await bridge.add_channel(event.channel.id)
# Create external media for streaming using the controller
external_media = await client.ari.create_external_media(
external_host="192.168.1.100:10000",
format="ulaw"
)
# Add external media to bridge and answer it
await bridge.add_channel(external_media.id)
await external_media.answer()
# Store bridge reference for cleanup
bridge_map[event.channel.id] = bridge
@client.on_stasis_end
async def on_stasis_end(event: StasisEndEvent):
# Clean up bridge when channel leaves
bridge = bridge_map.pop(event.channel.id, None)
if bridge:
await bridge.stop()
Example: IVR with DTMF
from ari_client import AriClient, StasisStartEvent, ChannelDtmfReceivedEvent
import logging
logger = logging.getLogger(__name__)
client = AriClient(
host="localhost", port=8088,
ari_user="asterisk", ari_password="asterisk"
)
@client.on_stasis_start
async def on_stasis_start(event: StasisStartEvent):
await event.channel.answer()
logger.info(f"Channel {event.channel.id} answered, waiting for DTMF...")
@client.on_channel_dtmf_received
async def on_dtmf(event: ChannelDtmfReceivedEvent):
logger.info(f"Digit '{event.digit}' received on channel {event.channel.id}")
if event.digit == "1":
# Send DTMF back to the channel
await event.channel.send_dtmf(dtmf="1234", between=100, duration=200)
elif event.digit == "2":
# Redirect to another endpoint
await event.channel.redirect(endpoint="PJSIP/operator")
elif event.digit == "3":
# Move to a different Stasis application
await event.channel.move(app="queue-handler", app_args="sales")
elif event.digit == "#":
await event.channel.stop()
Error Handling
The library includes automatic error handling:
- Event handler exceptions are automatically logged and don't crash the event listener
- HTTP API errors raise exceptions with descriptive messages
- WebSocket connection errors are logged and re-raised
Always wrap your operations in try-except blocks when appropriate:
@client.on_stasis_start
async def on_stasis_start(event: StasisStartEvent):
try:
await event.channel.answer()
except Exception as e:
logger.error(f"Failed to answer channel: {e}")
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 ari_client-0.7.1.tar.gz.
File metadata
- Download URL: ari_client-0.7.1.tar.gz
- Upload date:
- Size: 19.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.10.9 {"installer":{"name":"uv","version":"0.10.9","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
61571f35672dd3ea0958959aca28d6e059e660cc269c23ccd94ea32f4de27a6d
|
|
| MD5 |
d859c044302c5898e102d53f0f8f2f00
|
|
| BLAKE2b-256 |
750658c3bdbe6e06a74243494d7b5f75d75fdd11f336d4f8b0bb3e0dc2150f9c
|
File details
Details for the file ari_client-0.7.1-py3-none-any.whl.
File metadata
- Download URL: ari_client-0.7.1-py3-none-any.whl
- Upload date:
- Size: 17.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.10.9 {"installer":{"name":"uv","version":"0.10.9","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e1ca0ef23b49f65eaf45e3824c274c40da35e0a6ef96dd489b758a97ae2df92e
|
|
| MD5 |
3e24b992325c25cdddb7e48d7b4e9676
|
|
| BLAKE2b-256 |
caf11577a61580042df5d6db48723b97b8f5c996853842d7a92407a4407ad172
|