Metadata-Version: 2.4
Name: pyfreepbx
Version: 0.2.0
Summary: Typed Python library for FreePBX GraphQL API and Asterisk Manager Interface (AMI) — manage extensions, queues, and monitor PBX health.
Project-URL: Homepage, https://github.com/dannielperez/pyfreepbx
Project-URL: Repository, https://github.com/dannielperez/pyfreepbx
Project-URL: Documentation, https://github.com/dannielperez/pyfreepbx#readme
Project-URL: Issues, https://github.com/dannielperez/pyfreepbx/issues
Project-URL: Changelog, https://github.com/dannielperez/pyfreepbx/blob/main/CHANGELOG.md
Author: Daniel Perez
License-Expression: Apache-2.0
License-File: LICENSE
Keywords: ami,asterisk,call-center,freepbx,graphql,pbx,pjsip,sip,telephony,voip
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: System Administrators
Classifier: Intended Audience :: Telecommunications Industry
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Communications :: Telephony
Classifier: Topic :: System :: Monitoring
Classifier: Topic :: System :: Systems Administration
Classifier: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: httpx>=0.27
Requires-Dist: pydantic-settings>=2.0
Requires-Dist: pydantic>=2.0
Provides-Extra: dev
Requires-Dist: mypy>=1.10; extra == 'dev'
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: respx>=0.21; extra == 'dev'
Requires-Dist: ruff>=0.4; extra == 'dev'
Description-Content-Type: text/markdown

# pyfreepbx

[![CI](https://github.com/dperez/pyfreepbx/actions/workflows/ci.yml/badge.svg)](https://github.com/dperez/pyfreepbx/actions/workflows/ci.yml)
[![PyPI version](https://img.shields.io/pypi/v/pyfreepbx)](https://pypi.org/project/pyfreepbx/)
[![Python versions](https://img.shields.io/pypi/pyversions/pyfreepbx)](https://pypi.org/project/pyfreepbx/)
[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](LICENSE)
[![Typed](https://img.shields.io/badge/typing-typed-blue)](https://peps.python.org/pep-0561/)

A typed Python library for **Asterisk AMI** and **experimental FreePBX GraphQL** integration.

> **Status: Alpha (v0.x)** — AMI operations use well-documented, stable Asterisk actions and are reliable. GraphQL queries are provisional and need validation against a live FreePBX instance before they can be considered stable.

## What Works

| Layer | Stability | Description |
|-------|-----------|-------------|
| **AMI client** | **Stable** | Typed methods for Ping, CoreStatus, PJSIPShowEndpoints, QueueSummary, QueueStatus, QueueAdd, QueueRemove. Escape hatch for arbitrary actions. |
| **Health monitoring** | **Stable** | Endpoint registration, queue overview, interface health checks — degrades gracefully without AMI. |
| **Queue live ops** | **Stable** | Live stats, member listing, runtime add/remove (changes lost on Asterisk reload). |
| **System info** | **Stable** | Asterisk version, uptime, active calls via AMI CoreStatus. |
| **Extension read** | **Experimental** | GraphQL queries are unvalidated guesses — will likely need adjustment per instance. |
| **Queue config read** | **Experimental** | Queue module GraphQL support is undocumented and may not exist. |
| **Extension CRUD** | **Not implemented** | Raises `NotSupportedError`. FreePBX does not reliably expose write mutations. |

## Installation

```bash
pip install pyfreepbx
```

## Quick Start

```python
from pyfreepbx import FreePBX

with FreePBX.from_env() as pbx:

    # Connect AMI for reliable operational data
    if pbx.ami_available:
        pbx.connect_ami()

        # Queue stats (stable — AMI QueueSummary)
        for qs in pbx.queues.stats():
            print(f"{qs.queue}: {qs.callers} waiting, {qs.available} available")

        # Health overview (stable — AMI PJSIPShowEndpoints)
        summary = pbx.health.endpoint_summary()
        if summary:
            print(f"{summary.registered}/{summary.total} endpoints registered")

    # Extension listing (experimental — GraphQL, may need adjustment)
    for ext in pbx.extensions.list():
        print(f"{ext.extension}  {ext.name}")
```

Or with explicit configuration:

```python
pbx = FreePBX(
    host="pbx.example.com",
    api_token="your-api-token",
    ami_username="admin",       # optional — enables live stats
    ami_secret="your-secret",
)
```

## Environment Variables

| Variable | Required | Default | Description |
|---|---|---|---|
| `FREEPBX_HOST` | Yes | — | FreePBX hostname or IP |
| `FREEPBX_API_TOKEN` | Yes | — | Bearer token for GraphQL API |
| `FREEPBX_PORT` | No | `443` | HTTPS port |
| `FREEPBX_VERIFY_SSL` | No | `true` | Verify TLS certificates |
| `FREEPBX_TIMEOUT` | No | `30` | HTTP timeout (seconds) |
| `AMI_HOST` | No | `FREEPBX_HOST` | AMI hostname |
| `AMI_PORT` | No | `5038` | AMI TCP port |
| `AMI_USERNAME` | No | — | AMI login username |
| `AMI_SECRET` | No | — | AMI login secret |
| `AMI_TIMEOUT` | No | `10` | AMI socket timeout (seconds) |

## Architecture

```
FreePBX (facade)
├── .extensions   →  ExtensionService  →  FreePBXClient (GraphQL) ⚠ experimental
├── .queues       →  QueueService      →  FreePBXClient (config, ⚠ experimental) + AMIClient (live, stable)
├── .health       →  HealthService     →  FreePBXClient + AMIClient (stable)
└── .system       →  SystemService     →  AMIClient (stable)
```

**Design principles:**
- AMI for live operational state — this is the reliable backbone
- GraphQL API for configuration/inventory (experimental, instance-dependent)
- Services accept both clients; AMI is always optional
- `NotSupportedError` for operations not confirmed via schema introspection

## API Overview

### Health (stable)

```python
pbx.health.summary()                # → HealthSummary (always works)
pbx.health.pbx_info()               # → SystemInfo | None
pbx.health.endpoint_summary()       # → EndpointSummary | None
pbx.health.unregistered_endpoints() # → list[Device] | None
pbx.health.queue_overview()         # → list[QueueStats] | None
```

### Queues — live operations (stable, AMI)

```python
pbx.queues.stats()                       # → list[QueueStats]
pbx.queues.members("400")                # → list[QueueMember]
pbx.queues.add_member_runtime(...)       # AMI QueueAdd (lost on reload)
pbx.queues.remove_member_runtime(...)    # AMI QueueRemove (lost on reload)
```

### Queues — config (experimental, GraphQL)

```python
pbx.queues.list()              # → list[Queue]        ⚠ provisional query
pbx.queues.get("400")          # → Queue              ⚠ provisional query
```

### System (stable, AMI)

```python
pbx.system.info()       # → SystemInfo (AMI CoreStatus)
```

### Extensions (experimental, GraphQL)

```python
pbx.extensions.list()          # → list[Extension]    ⚠ provisional query
pbx.extensions.get("1001")     # → Extension          ⚠ provisional query
# create/update/enable/disable → NotSupportedError
```

## Development

```bash
git clone https://github.com/dperez/pyfreepbx.git
cd pyfreepbx
pipenv install --dev
```

```bash
pipenv run pytest                                  # run tests
pipenv run pytest --cov=pyfreepbx --cov-report=term-missing  # with coverage
pipenv run ruff check src/ tests/                  # lint
pipenv run mypy src/                               # type check
```

## Known Limitations

- **GraphQL queries are provisional** — field names, query names, and response structures are educated guesses based on FreePBX 16/17 documentation. Validate via `{ __schema { queryType { fields { name } } } }` on your instance. Methods emit `UserWarning` at runtime.
- **No extension CRUD** — `create`, `update`, `enable`, `disable` raise `NotSupportedError`. FreePBX does not reliably expose write mutations via the GraphQL API.
- **Runtime-only queue membership** — `add_member_runtime()` / `remove_member_runtime()` changes are lost on Asterisk reload or restart.
- **`sip_peers()` is deprecated** — chan_sip was removed in Asterisk 21. Emits `DeprecationWarning`. Will be removed in v0.2.0.
- **No async support** — planned for a future release.
- **No ARI or direct DB** — intentionally out of scope for v0.1.0.

## Contributing

Contributions are welcome. Please open an issue first to discuss what you'd like to change.

See [CONTRIBUTING.md](CONTRIBUTING.md) for development guidelines.

## License

[Apache-2.0](LICENSE) — Copyright 2026 Daniel Perez
