Skip to content

Commit 45e87bc

Browse files
committed
Version 0.0.1
1 parent f98f1bf commit 45e87bc

11 files changed

Lines changed: 616 additions & 1 deletion

File tree

.env_example

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#################################
2+
########### ZAMMADCTI ###########
3+
###### url (Required)
4+
####
5+
## url (str): URL of CTI integrator in Zammad.
6+
## Default=None
7+
####
8+
ZAMMADCTI_URL=
9+
10+
###### verify_ssl
11+
####
12+
## verify_ssl (bool): Verify server's SSL certificate on requests.
13+
## Default=False
14+
####
15+
ZAMMADCTI_VERIFY_SSL=
16+
17+
#################################
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
name: Publish to PyPI
2+
3+
on:
4+
release:
5+
types: [published]
6+
7+
permissions:
8+
contents: read
9+
id-token: write
10+
11+
jobs:
12+
build-and-publish:
13+
runs-on: ubuntu-latest
14+
steps:
15+
# Check out the repository
16+
- name: Checkout code
17+
uses: actions/checkout@v4
18+
19+
# Set up Python
20+
- name: Set up Python
21+
uses: actions/setup-python@v5
22+
with:
23+
python-version: "3.x"
24+
25+
# Install build tools
26+
- name: Install build tools
27+
run: |
28+
python -m pip install --upgrade pip setuptools wheel build twine
29+
30+
# Build the distribution files
31+
- name: Build the package
32+
run: python -m build
33+
34+
# Publish to PyPI
35+
- name: Publish to PyPI
36+
uses: pypa/gh-action-pypi-publish@release/v1
37+
with:
38+
packages-dir: dist/
39+
env:
40+
PYPI_API_TOKEN: ${{ secrets.PYPI_API_TOKEN }}

README.md

Lines changed: 239 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,239 @@
1-
# Python Zammad CTI
1+
# Zammad CTI Client (Python)
2+
3+
A lightweight, typed Python client for interacting with **Zammad’s Generic CTI API**.
4+
This module allows telephony systems (PBX, SIP servers, dialers, etc.) to notify Zammad about call lifecycle events such as **new calls**, **answers**, and **hangups**.
5+
6+
📖 Official Zammad CTI documentation:
7+
[https://docs.zammad.org/en/latest/api/generic-cti/index.html](https://docs.zammad.org/en/latest/api/generic-cti/index.html)
8+
9+
---
10+
11+
## Features
12+
13+
* Simple and clean API for Zammad CTI events
14+
* Fully typed (using `typing` and custom type aliases)
15+
* Automatic environment-based configuration via `ENVMod`
16+
* Structured logging via `logwrap`
17+
* Minimal dependencies
18+
* Explicit control over SSL verification
19+
* Designed for backend / PBX integrations
20+
21+
---
22+
23+
## Installation
24+
25+
```bash
26+
pip install py-zammad-cti
27+
```
28+
29+
This module also depends on:
30+
31+
* `classmods`
32+
* `requests`
33+
* `python-dotenv` (Optional)
34+
35+
Make sure those are available in your environment.
36+
37+
---
38+
39+
## Basic Usage
40+
41+
```python
42+
from zammad_cti import CTIClient
43+
44+
client = CTIClient(
45+
url="https://zammad.example.com/api/v1/cti/<TOKEN>",
46+
verify_ssl=False,
47+
)
48+
```
49+
50+
Once initialized, you can send CTI events to Zammad.
51+
52+
---
53+
54+
## Environment Configuration (ENVMod)
55+
56+
The `CTIClient` constructor is registered with `ENVMod`:
57+
58+
```python
59+
@ENVMod.register(section_name='ZammadCTI')
60+
```
61+
62+
This allows configuration via environment variables or `.env` files (depending on your `ENVMod` setup), for example:
63+
64+
```env
65+
ZammadCTI_URL=https://zammad.example.com/api/v1/cti/<TOKEN>
66+
ZammadCTI_VERIFY_SSL=false
67+
```
68+
69+
This makes the client easy to configure in containerized or production environments.
70+
71+
Later you can load env with python-dotenv and classmod easily:
72+
73+
```python
74+
from zammad_cti import CTIClient
75+
from classmods import ENVMod
76+
77+
client = CTIClient(**ENVmod.load_args(CTIClient.__init__))
78+
```
79+
80+
Read classmods documentations for more info and usage:
81+
82+
📖 Official Zammad CTI documentation:
83+
[https://github.com/hmohammad2520-org/classmods](https://github.com/hmohammad2520-org/classmods)
84+
85+
---
86+
87+
## Call Lifecycle Methods
88+
89+
### 1. New Call
90+
91+
Notify Zammad that a new call has started.
92+
93+
```python
94+
client.new_call(
95+
_from="+491234567",
96+
to="+498765432",
97+
direction=CallDirection.IN,
98+
call_id="call-uuid-123",
99+
user="John Doe",
100+
queue="Support",
101+
)
102+
```
103+
104+
**Parameters**
105+
106+
| Name | Type | Description | |
107+
| ----------- | --------------- | ----------------------------- | --------------- |
108+
| `_from` | `str` | Caller number | |
109+
| `to` | `str` | Destination number | |
110+
| `direction` | `CallDirection` | Call direction (`in` / `out`) | |
111+
| `call_id` | `str` | Unique call identifier | |
112+
| `user` | `Optional[str | List[str]]` | Related user(s) |
113+
| `queue` | `Optional[str]` | Queue name | |
114+
115+
---
116+
117+
### 2. Answer Call
118+
119+
Notify Zammad that a call has been answered.
120+
121+
```python
122+
client.answer(
123+
_from="+491234567",
124+
to="+498765432",
125+
direction=CallDirection.IN,
126+
call_id="call-uuid-123",
127+
answering_number="+498765432",
128+
user="Agent Smith",
129+
)
130+
```
131+
132+
**Parameters**
133+
134+
| Name | Type | Description | |
135+
| ------------------ | --------------- | ------------------------------------------ | ---------------- |
136+
| `_from` | `str` | Caller number | |
137+
| `to` | `str` | Destination number | |
138+
| `direction` | `CallDirection` | Call direction | |
139+
| `call_id` | `str` | Unique call ID | |
140+
| `answering_number` | `Optional[str]` | Number used to identify the answering user | |
141+
| `user` | `Optional[str | List[str]]` | User(s) involved |
142+
143+
---
144+
145+
### 3. Hangup Call
146+
147+
Notify Zammad that a call has ended.
148+
149+
```python
150+
client.hangup(
151+
_from="+491234567",
152+
to="+498765432",
153+
direction=CallDirection.IN,
154+
call_id="call-uuid-123",
155+
cause=HangupCause.NORMAL_CLEARING,
156+
answering_number="+498765432",
157+
)
158+
```
159+
160+
**Parameters**
161+
162+
| Name | Type | Description |
163+
| ------------------ | --------------- | ------------------ |
164+
| `_from` | `str` | Caller number |
165+
| `to` | `str` | Destination number |
166+
| `direction` | `CallDirection` | Call direction |
167+
| `call_id` | `str` | Unique call ID |
168+
| `cause` | `HangupCause` | Reason for hangup |
169+
| `answering_number` | `Optional[str]` | User lookup hint |
170+
171+
Zammad uses the hangup cause to determine missed calls, answered calls, etc.
172+
173+
---
174+
175+
## Logging
176+
177+
This module uses `logwrap` decorators to automatically log:
178+
179+
* Outgoing HTTP requests
180+
* Call lifecycle transitions
181+
* Returned results
182+
183+
Example logged message:
184+
185+
```
186+
Changed call state to hangup: {...}, result: {...}
187+
```
188+
189+
Logging behavior can be controlled centrally via `logwrap`.
190+
191+
---
192+
193+
## HTTP Behavior
194+
195+
* Uses a persistent `requests.Session`
196+
* Sends data as `application/json`
197+
* Automatically removes `None` values from payloads
198+
* Raises exceptions on HTTP errors (`response.raise_for_status()`)
199+
200+
Most CTI responses are empty unless the call is rejected or blocked in Zammad. Read Zammad CTI system documentation for more info.
201+
202+
---
203+
204+
## Type Safety
205+
206+
The client relies on explicit type aliases:
207+
208+
* `CallDirection`
209+
* `HangupCause`
210+
211+
This helps prevent invalid CTI payloads and improves IDE autocomplete and static analysis.
212+
213+
---
214+
215+
## Typical Use Case
216+
217+
This client is ideal for:
218+
219+
* PBX → Zammad integrations
220+
* SIP servers (Asterisk, FreeSWITCH, Kamailio)
221+
* Custom softphones
222+
* Call monitoring services
223+
* Telephony middleware
224+
225+
---
226+
227+
## Error Handling
228+
229+
* All HTTP errors raise `requests.HTTPError`
230+
* Validation is delegated to Zammad
231+
* Invalid payloads will be rejected server-side
232+
233+
You should wrap calls in your own retry / error-handling logic if required.
234+
235+
---
236+
237+
## License
238+
239+
MIT (or your preferred license)

pyproject.toml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
[build-system]
2+
requires = ["setuptools>=42", "wheel"]
3+
build-backend = "setuptools.build_meta"
4+
5+
[project]
6+
name = "py-zammad-cti"
7+
version = "0.0.1"
8+
description = "Zammad CTI client"
9+
readme = "README.md"
10+
license = { file = "LICENSE" }
11+
authors = [
12+
{ name = "hmohammad", email = "hmohammad2520@gmail.com" }
13+
]
14+
keywords = ["python", "library", "zammad", "cti"]
15+
classifiers = [
16+
"Programming Language :: Python :: 3",
17+
"License :: OSI Approved :: MIT License",
18+
"Operating System :: OS Independent",
19+
]
20+
dependencies = [
21+
"requests==2.32.4",
22+
"classmods==1.2.1"
23+
]
24+
requires-python = ">=3.9"
25+
26+
[project.urls]
27+
Homepage = "https://github.com/hmohammad2520-org/py-zammad-cti"
28+
BugTracker = "https://github.com/hmohammad2520-org/py-zammad-cti"

requirements.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
requests==2.32.4
2+
classmods==1.2.1
3+
4+
python-dotenv
5+
setuptools
6+
pytest

setup.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
from setuptools import find_packages, setup
2+
3+
setup(
4+
name='py-zammad-cti',
5+
version='0.0.1',
6+
license="MIT",
7+
description='Zammad CTI interface',
8+
author='hmohammad',
9+
author_email='hmohammad2520@gmail.com',
10+
url='https://github.com/hmohammad2520-org/py-zammad-cti',
11+
install_requires=[
12+
'requests==2.32.4',
13+
'classmods==1.2.1'
14+
],
15+
packages=find_packages(exclude=['test', 'test.*']),
16+
include_package_data=True,
17+
zip_safe=False,
18+
)

test/test_env.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from classmods import ENVMod
2+
from zammad_cti import CTIClient as _
3+
4+
5+
def test_create_env():
6+
ENVMod.save_example()
7+
ENVMod.sync_env_file()

zammad_cti/__init__.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
from ._client import CTIClient
2+
from .__version__ import __version__ as version
3+
4+
5+
__all__ = [
6+
'CTIClient',
7+
'version',
8+
]

zammad_cti/__version__.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
__version__ = '0.0.1'
2+
3+
def get_version():
4+
return __version__
5+
6+
__all__ = [
7+
'get_version',
8+
]

0 commit comments

Comments
 (0)