sipx 0.0.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. sipx-0.0.4/PKG-INFO +319 -0
  2. sipx-0.0.4/README.md +311 -0
  3. sipx-0.0.4/pyproject.toml +55 -0
  4. sipx-0.0.4/setup.cfg +4 -0
  5. sipx-0.0.4/sipx/__init__.py +299 -0
  6. sipx-0.0.4/sipx/_depends.py +219 -0
  7. sipx-0.0.4/sipx/_events.py +337 -0
  8. sipx-0.0.4/sipx/_routing.py +126 -0
  9. sipx-0.0.4/sipx/_types.py +265 -0
  10. sipx-0.0.4/sipx/_uri.py +219 -0
  11. sipx-0.0.4/sipx/_utils.py +187 -0
  12. sipx-0.0.4/sipx/_version.py +3 -0
  13. sipx-0.0.4/sipx/client/__init__.py +4 -0
  14. sipx-0.0.4/sipx/client/_async.py +640 -0
  15. sipx-0.0.4/sipx/client/_base.py +184 -0
  16. sipx-0.0.4/sipx/client/_sync.py +1172 -0
  17. sipx-0.0.4/sipx/contrib/__init__.py +57 -0
  18. sipx-0.0.4/sipx/contrib/_fastapi.py +169 -0
  19. sipx-0.0.4/sipx/contrib/_isup.py +740 -0
  20. sipx-0.0.4/sipx/contrib/_sipi.py +258 -0
  21. sipx-0.0.4/sipx/contrib/_sipi_br.py +383 -0
  22. sipx-0.0.4/sipx/contrib/_stt_whisper.py +28 -0
  23. sipx-0.0.4/sipx/contrib/_tts_google.py +38 -0
  24. sipx-0.0.4/sipx/contrib/ivr/__init__.py +5 -0
  25. sipx-0.0.4/sipx/contrib/ivr/_async.py +93 -0
  26. sipx-0.0.4/sipx/contrib/ivr/_models.py +55 -0
  27. sipx-0.0.4/sipx/contrib/ivr/_sync.py +90 -0
  28. sipx-0.0.4/sipx/dns/__init__.py +5 -0
  29. sipx-0.0.4/sipx/dns/_async.py +122 -0
  30. sipx-0.0.4/sipx/dns/_models.py +19 -0
  31. sipx-0.0.4/sipx/dns/_sync.py +124 -0
  32. sipx-0.0.4/sipx/fsm/__init__.py +13 -0
  33. sipx-0.0.4/sipx/fsm/_manager.py +452 -0
  34. sipx-0.0.4/sipx/fsm/_models.py +386 -0
  35. sipx-0.0.4/sipx/fsm/_timer.py +142 -0
  36. sipx-0.0.4/sipx/main.py +239 -0
  37. sipx-0.0.4/sipx/media/__init__.py +48 -0
  38. sipx-0.0.4/sipx/media/_async.py +426 -0
  39. sipx-0.0.4/sipx/media/_audio.py +103 -0
  40. sipx-0.0.4/sipx/media/_codecs.py +258 -0
  41. sipx-0.0.4/sipx/media/_dtmf.py +242 -0
  42. sipx-0.0.4/sipx/media/_generators.py +225 -0
  43. sipx-0.0.4/sipx/media/_opus.py +111 -0
  44. sipx-0.0.4/sipx/media/_pyaudio.py +192 -0
  45. sipx-0.0.4/sipx/media/_rtp.py +365 -0
  46. sipx-0.0.4/sipx/media/_session.py +233 -0
  47. sipx-0.0.4/sipx/media/_stt.py +39 -0
  48. sipx-0.0.4/sipx/media/_tts.py +72 -0
  49. sipx-0.0.4/sipx/media/audio/__init__.py +27 -0
  50. sipx-0.0.4/sipx/media/codecs/__init__.py +4 -0
  51. sipx-0.0.4/sipx/media/dtmf/__init__.py +17 -0
  52. sipx-0.0.4/sipx/media/rtp/__init__.py +4 -0
  53. sipx-0.0.4/sipx/media/session/__init__.py +4 -0
  54. sipx-0.0.4/sipx/models/__init__.py +60 -0
  55. sipx-0.0.4/sipx/models/_auth.py +811 -0
  56. sipx-0.0.4/sipx/models/_body.py +1420 -0
  57. sipx-0.0.4/sipx/models/_header.py +535 -0
  58. sipx-0.0.4/sipx/models/_message.py +965 -0
  59. sipx-0.0.4/sipx/server/__init__.py +6 -0
  60. sipx-0.0.4/sipx/server/_async.py +116 -0
  61. sipx-0.0.4/sipx/server/_base.py +156 -0
  62. sipx-0.0.4/sipx/server/_sync.py +234 -0
  63. sipx-0.0.4/sipx/session/__init__.py +13 -0
  64. sipx-0.0.4/sipx/session/_subscription.py +315 -0
  65. sipx-0.0.4/sipx/session/_timer.py +375 -0
  66. sipx-0.0.4/sipx/transports/__init__.py +66 -0
  67. sipx-0.0.4/sipx/transports/_base.py +235 -0
  68. sipx-0.0.4/sipx/transports/_tcp.py +572 -0
  69. sipx-0.0.4/sipx/transports/_tls.py +683 -0
  70. sipx-0.0.4/sipx/transports/_udp.py +461 -0
  71. sipx-0.0.4/sipx/transports/_utils.py +28 -0
  72. sipx-0.0.4/sipx/transports/_ws.py +286 -0
  73. sipx-0.0.4/sipx.egg-info/PKG-INFO +319 -0
  74. sipx-0.0.4/sipx.egg-info/SOURCES.txt +156 -0
  75. sipx-0.0.4/sipx.egg-info/dependency_links.txt +1 -0
  76. sipx-0.0.4/sipx.egg-info/entry_points.txt +2 -0
  77. sipx-0.0.4/sipx.egg-info/requires.txt +1 -0
  78. sipx-0.0.4/sipx.egg-info/top_level.txt +1 -0
  79. sipx-0.0.4/tests/test_client.py +406 -0
  80. sipx-0.0.4/tests/test_contrib.py +85 -0
  81. sipx-0.0.4/tests/test_depends.py +149 -0
  82. sipx-0.0.4/tests/test_dns.py +255 -0
  83. sipx-0.0.4/tests/test_events.py +369 -0
  84. sipx-0.0.4/tests/test_fsm.py +484 -0
  85. sipx-0.0.4/tests/test_routing.py +119 -0
  86. sipx-0.0.4/tests/test_server.py +205 -0
  87. sipx-0.0.4/tests/test_session_timer.py +245 -0
  88. sipx-0.0.4/tests/test_sipi_br.py +257 -0
  89. sipx-0.0.4/tests/test_subscription.py +189 -0
  90. sipx-0.0.4/tests/test_uri.py +226 -0
sipx-0.0.4/PKG-INFO ADDED
@@ -0,0 +1,319 @@
1
+ Metadata-Version: 2.4
2
+ Name: sipx
3
+ Version: 0.0.4
4
+ Summary: Add your description here
5
+ Requires-Python: >=3.13
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: rich>=14.1.0
8
+
9
+ # sipx
10
+
11
+ [![Python 3.12+](https://img.shields.io/badge/python-3.12+-blue.svg)](https://www.python.org/downloads/)
12
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
13
+
14
+ A modern, high-performance SIP library for Python inspired by [httpx](https://www.python-httpx.org/).
15
+
16
+ Build voice automation scripts, IVR systems, AI-powered call handlers, and SIP testing tools with a clean, Pythonic API.
17
+
18
+ ## Features
19
+
20
+ - **httpx-inspired API** — `client.invite()`, `client.register()`, sync and async
21
+ - **FastAPI-style server** — `@server.invite`, `Annotated` DI extractors
22
+ - **Response builders** — `request.ok()`, `request.trying()`, `request.error(code)`
23
+ - **Dialog tracking** — `client.ack()` / `client.bye()` without passing response
24
+ - **Declarative events** — `@on('INVITE', status=200)` decorators
25
+ - **Full SIP support** — 14 methods, auto 100 Trying, auto DNS SRV resolution
26
+ - **Digest authentication** — MD5, SHA-256, auto-retry on 401/407
27
+ - **RTP + DTMF** — RFC 4733 + SIP INFO + Inband, sync and async
28
+ - **SDP with ICE/SRTP** — RFC 4566, offer/answer, ICE candidates, DTLS
29
+ - **Media pipeline** — RTP, tone generators, TTS/STT adapters, IVR builder
30
+ - **SIP-I** — Real ISUP binary encoding (ITU-T Q.763), Brazilian SIP-I (ANATEL)
31
+ - **Multiple transports** — UDP, TCP, TLS, WebSocket (sync + async)
32
+ - **State management** — Transaction FSMs with Timer A/E retransmission
33
+ - **Extensible** — ABC-based transports, body parsers, custom DI extractors
34
+
35
+ ## Quick Start
36
+
37
+ ```python
38
+ import sipx
39
+
40
+ # One-liners
41
+ sipx.options("sip:pbx.example.com")
42
+ sipx.register("sip:alice@pbx.com", auth=("alice", "secret"))
43
+ sipx.send("sip:bob@pbx.com", "Hello!", auth=("alice", "secret"))
44
+
45
+ # Full call with dialog tracking
46
+ from sipx import Client
47
+
48
+ with Client(local_port=5061) as client:
49
+ client.auth = ("alice", "secret")
50
+
51
+ client.register("sip:alice@pbx.com")
52
+
53
+ sdp = client.create_sdp(port=8000)
54
+ r = client.invite("sip:bob@pbx.com", body=sdp.to_string())
55
+
56
+ if r.status_code == 200:
57
+ client.ack() # uses tracked dialog
58
+ # call is active...
59
+ client.bye() # uses tracked dialog
60
+ ```
61
+
62
+ ## Installation
63
+
64
+ ```bash
65
+ # With uv (recommended)
66
+ uv add sipx
67
+
68
+ # With pip
69
+ pip install sipx
70
+
71
+ # From source
72
+ git clone https://github.com/yourusername/sipx.git
73
+ cd sipx
74
+ uv sync
75
+ ```
76
+
77
+ ### Requirements
78
+
79
+ Python 3.12+
80
+
81
+ ## Usage Examples
82
+
83
+ ### SIP Server (FastAPI-style decorators + DI)
84
+
85
+ ```python
86
+ from typing import Annotated
87
+ from sipx import SIPServer, Request, FromHeader, Header
88
+
89
+ server = SIPServer(local_host="127.0.0.1", local_port=5060)
90
+
91
+ @server.register
92
+ def on_register(request: Request, caller: Annotated[str, FromHeader]):
93
+ print(f"REGISTER from {caller}")
94
+ return request.ok()
95
+
96
+ @server.message
97
+ def on_message(request: Request, caller: Annotated[str, FromHeader]):
98
+ body = request.content.decode() if request.content else ""
99
+ print(f"MESSAGE from {caller}: {body}")
100
+ return request.ok()
101
+
102
+ server.start()
103
+ ```
104
+
105
+ ### Async Client
106
+
107
+ ```python
108
+ import asyncio
109
+ from sipx import AsyncClient
110
+
111
+ async def main():
112
+ async with AsyncClient(local_port=5061) as client:
113
+ client.auth = ("alice", "secret")
114
+
115
+ r = await client.register("sip:alice@pbx.com")
116
+ print(f"REGISTER: {r.status_code}")
117
+
118
+ sdp = client.create_sdp(port=8000)
119
+ r = await client.invite("sip:bob@pbx.com", body=sdp.to_string())
120
+ if r and r.status_code == 200:
121
+ await client.ack()
122
+ await asyncio.sleep(5)
123
+ await client.bye()
124
+
125
+ asyncio.run(main())
126
+ ```
127
+
128
+ ### FastAPI + SIP Integration
129
+
130
+ Run a REST API that controls SIP operations:
131
+
132
+ ```bash
133
+ # Terminal 1: SIP server
134
+ uv run python examples/server.py
135
+
136
+ # Terminal 2: FastAPI
137
+ uv run python examples/fastapi_sip.py
138
+
139
+ # Terminal 3: test via curl
140
+ curl -X POST http://localhost:8000/message \
141
+ -H "Content-Type: application/json" \
142
+ -d '{"uri":"sip:test@127.0.0.1:15090","content":"Hello from FastAPI!"}' | jq
143
+ ```
144
+
145
+ ```json
146
+ {
147
+ "status_code": 200,
148
+ "reason": "OK"
149
+ }
150
+ ```
151
+
152
+ Full request/response trace with retransmission timers, DI resolution, and FSM state transitions:
153
+
154
+ ```text
155
+ # FastAPI (client side)
156
+ INFO UDP bound to 0.0.0.0:40703
157
+ DEBUG Transaction 592b5fee created: MESSAGE (type=NON_INVITE)
158
+ DEBUG Timer E started (0.5s) # retransmission armed
159
+ DEBUG Timer F started (32.0s) # timeout armed
160
+ DEBUG UDP send 335 bytes -> 127.0.0.1:15090
161
+ DEBUG UDP recv 245 bytes <- 127.0.0.1:15090
162
+ DEBUG <<< 200 OK
163
+ DEBUG Transaction 592b5fee: TRYING -> COMPLETED
164
+ DEBUG Timer E cancelled # retransmission stopped
165
+ DEBUG Timer F cancelled # timeout stopped
166
+
167
+ # SIP Server (server side)
168
+ DEBUG UDP recv 335 bytes <- 127.0.0.1:40703
169
+ DEBUG Resolving handler on_message
170
+ DEBUG Extracting caller via FromHeader()
171
+ MESSAGE from <sip:user@0.0.0.0>: Hello from FastAPI!
172
+ DEBUG >>> SENDING 200 OK to 127.0.0.1:40703
173
+ DEBUG UDP send 245 bytes -> 127.0.0.1:40703
174
+ ```
175
+
176
+ ### Event Handlers
177
+
178
+ ```python
179
+ from sipx import Events, on
180
+
181
+ class CallEvents(Events):
182
+ @on('INVITE', status=200)
183
+ def on_call_ok(self, request, response, context):
184
+ print("Call accepted!")
185
+
186
+ @on('INVITE', status=(180, 183))
187
+ def on_ringing(self, request, response, context):
188
+ print(f"Ringing... ({response.status_code})")
189
+
190
+ @on(status=(401, 407))
191
+ def on_auth(self, request, response, context):
192
+ print("Auth challenge received")
193
+ ```
194
+
195
+ ### Authentication
196
+
197
+ ```python
198
+ # Tuple auth (auto-retry on 401/407)
199
+ client.auth = ("alice", "secret")
200
+
201
+ # Or explicit Auth object
202
+ client.auth = Auth.Digest("alice", "secret")
203
+
204
+ # Per-request override
205
+ r = client.register("sip:alice@pbx.com")
206
+ if r.status_code == 401:
207
+ r = client.retry_with_auth(r, auth=Auth.Digest("alice", "other"))
208
+ ```
209
+
210
+ ## Supported Methods
211
+
212
+ | Method | Description | Example |
213
+ | --------- | ---------------------- | ------------------------------------------------------------- |
214
+ | INVITE | Establish a call | `client.invite('sip:bob@ex.com', body=sdp)` |
215
+ | ACK | Acknowledge INVITE | `client.ack()` (auto dialog) |
216
+ | BYE | Terminate a call | `client.bye()` (auto dialog) |
217
+ | CANCEL | Cancel pending INVITE | `client.cancel(response=r)` |
218
+ | REGISTER | Register location | `client.register('sip:alice@ex.com')` |
219
+ | OPTIONS | Query capabilities | `client.options('sip:ex.com')` |
220
+ | MESSAGE | Instant message | `client.message('sip:bob@ex.com', content='Hi')` |
221
+ | SUBSCRIBE | Subscribe to events | `client.subscribe('sip:bob@ex.com')` |
222
+ | NOTIFY | Event notification | `client.notify('sip:bob@ex.com')` |
223
+ | REFER | Call transfer | `client.refer('sip:bob@ex.com', refer_to='sip:carol@ex.com')` |
224
+ | INFO | Mid-dialog info (DTMF) | `client.info('sip:bob@ex.com', content=dtmf)` |
225
+ | UPDATE | Update session | `client.update('sip:bob@ex.com', sdp_content=sdp)` |
226
+ | PRACK | Provisional ACK | `client.prack(response=r)` |
227
+ | PUBLISH | Publish state | `client.publish('sip:bob@ex.com')` |
228
+
229
+ ## RFC Compliance
230
+
231
+ | RFC | Title | Status |
232
+ | ---- | --------------------------------- | ------------------------------------------ |
233
+ | 3261 | SIP: Session Initiation Protocol | Complete (auto 100 Trying, retransmission) |
234
+ | 2617 | HTTP Digest Authentication | Complete (MD5, SHA-256) |
235
+ | 7616 | HTTP Digest (SHA-256) | Complete |
236
+ | 4566 | SDP: Session Description Protocol | Complete (ICE, SRTP, DTLS) |
237
+ | 3264 | Offer/Answer Model with SDP | Complete |
238
+ | 3550 | RTP: Real-time Transport Protocol | Complete (sync + async) |
239
+ | 4733 | DTMF via RTP (telephone-event) | Complete |
240
+ | 3263 | DNS SRV Resolution | Complete (auto in Client) |
241
+ | 4028 | Session Timers | Complete |
242
+
243
+ ## Documentation
244
+
245
+ | Document | Description |
246
+ | ---------------------------------------- | ------------------------------------------------------- |
247
+ | [docs/SDD.md](docs/SDD.md) | Software Design Document (full spec, diagrams, roadmap) |
248
+ | [examples/README.md](examples/README.md) | Examples catalog (22 examples) |
249
+ | [docker/asterisk/](docker/asterisk/) | Asterisk test environment |
250
+
251
+ ## Testing with Asterisk
252
+
253
+ ```bash
254
+ cd docker/asterisk
255
+ docker-compose up -d
256
+ uv run python examples/asterisk.py
257
+ ```
258
+
259
+ ## Examples
260
+
261
+ See [examples/README.md](examples/README.md) for the full list. Highlights:
262
+
263
+ | Example | What it shows |
264
+ | ----------------------------------------------------- | ---------------------------------------- |
265
+ | [quickstart.py](examples/quickstart.py) | One-liner register, options, call, send |
266
+ | [call.py](examples/call.py) | REGISTER -> INVITE -> ACK -> BYE |
267
+ | [server.py](examples/server.py) | SIPServer with decorators + DI |
268
+ | [ivr.py](examples/ivr.py) | Async IVR with RTP + DTMF |
269
+ | [response_builders.py](examples/response_builders.py) | request.ok/trying/error, dialog tracking |
270
+ | [asterisk.py](examples/asterisk.py) | Comprehensive integration test |
271
+
272
+ ## Roadmap
273
+
274
+ See [docs/SDD.md](docs/SDD.md) for the full roadmap. Key upcoming work:
275
+
276
+ - **SRTP** — packet encryption (RFC 3711)
277
+ - **RTCP** — control protocol (RFC 3550 Section 6)
278
+ - **Jitter buffer** — for smooth RTP playback
279
+ - **Coverage >80%** — currently 607 tests, ~60%
280
+ - **PyPI publishing** — package ready, CI pipeline configured
281
+
282
+ ## Development
283
+
284
+ ```bash
285
+ git clone https://github.com/yourusername/sipx.git
286
+ cd sipx
287
+ uv sync
288
+ uv run ruff check sipx
289
+ uv run pytest
290
+ ```
291
+
292
+ ## Inspirations
293
+
294
+ API design:
295
+
296
+ - [httpx](https://www.python-httpx.org/) — Pythonic API, sync/async, extensible
297
+
298
+ Reference implementations:
299
+
300
+ - [sipd](https://github.com/initbar/sipd) — SIP daemon implementation
301
+ - [sipmessage](https://github.com/spacinov/sipmessage) — SIP message parsing
302
+ - [sip-parser](https://github.com/alxgb/sip-parser) — SIP parser
303
+ - [PySipIvr](https://github.com/ersansrck/PySipIvr) — SIP IVR implementation
304
+ - [sip-resources](https://github.com/miconda/sip-resources) — Curated SIP resources and documentation
305
+
306
+ Other inspirations:
307
+
308
+ - [pyVoIP](https://github.com/tayler6000/pyVoIP) — VoIP functionality
309
+ - [aiosip](https://github.com/Eyepea/aiosip) — Async SIP
310
+ - [b2bua](https://github.com/sippy/b2bua) — Back-to-back user agent
311
+ - [pysipp](https://github.com/SIPp/pysipp) — SIP testing
312
+ - [PySIPio](https://pypi.org/project/PySIPio/) — SIP I/O
313
+ - [katariSIP](https://github.com/klocation/katarisip) — SIP library
314
+ - [SIP-Auth-helper](https://github.com/pbertera/SIP-Auth-helper) — SIP auth tools
315
+ - [callsip.py](https://github.com/rundekugel/callsip.py) — Simple SIP caller
316
+
317
+ ## License
318
+
319
+ MIT License
sipx-0.0.4/README.md ADDED
@@ -0,0 +1,311 @@
1
+ # sipx
2
+
3
+ [![Python 3.12+](https://img.shields.io/badge/python-3.12+-blue.svg)](https://www.python.org/downloads/)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+
6
+ A modern, high-performance SIP library for Python inspired by [httpx](https://www.python-httpx.org/).
7
+
8
+ Build voice automation scripts, IVR systems, AI-powered call handlers, and SIP testing tools with a clean, Pythonic API.
9
+
10
+ ## Features
11
+
12
+ - **httpx-inspired API** — `client.invite()`, `client.register()`, sync and async
13
+ - **FastAPI-style server** — `@server.invite`, `Annotated` DI extractors
14
+ - **Response builders** — `request.ok()`, `request.trying()`, `request.error(code)`
15
+ - **Dialog tracking** — `client.ack()` / `client.bye()` without passing response
16
+ - **Declarative events** — `@on('INVITE', status=200)` decorators
17
+ - **Full SIP support** — 14 methods, auto 100 Trying, auto DNS SRV resolution
18
+ - **Digest authentication** — MD5, SHA-256, auto-retry on 401/407
19
+ - **RTP + DTMF** — RFC 4733 + SIP INFO + Inband, sync and async
20
+ - **SDP with ICE/SRTP** — RFC 4566, offer/answer, ICE candidates, DTLS
21
+ - **Media pipeline** — RTP, tone generators, TTS/STT adapters, IVR builder
22
+ - **SIP-I** — Real ISUP binary encoding (ITU-T Q.763), Brazilian SIP-I (ANATEL)
23
+ - **Multiple transports** — UDP, TCP, TLS, WebSocket (sync + async)
24
+ - **State management** — Transaction FSMs with Timer A/E retransmission
25
+ - **Extensible** — ABC-based transports, body parsers, custom DI extractors
26
+
27
+ ## Quick Start
28
+
29
+ ```python
30
+ import sipx
31
+
32
+ # One-liners
33
+ sipx.options("sip:pbx.example.com")
34
+ sipx.register("sip:alice@pbx.com", auth=("alice", "secret"))
35
+ sipx.send("sip:bob@pbx.com", "Hello!", auth=("alice", "secret"))
36
+
37
+ # Full call with dialog tracking
38
+ from sipx import Client
39
+
40
+ with Client(local_port=5061) as client:
41
+ client.auth = ("alice", "secret")
42
+
43
+ client.register("sip:alice@pbx.com")
44
+
45
+ sdp = client.create_sdp(port=8000)
46
+ r = client.invite("sip:bob@pbx.com", body=sdp.to_string())
47
+
48
+ if r.status_code == 200:
49
+ client.ack() # uses tracked dialog
50
+ # call is active...
51
+ client.bye() # uses tracked dialog
52
+ ```
53
+
54
+ ## Installation
55
+
56
+ ```bash
57
+ # With uv (recommended)
58
+ uv add sipx
59
+
60
+ # With pip
61
+ pip install sipx
62
+
63
+ # From source
64
+ git clone https://github.com/yourusername/sipx.git
65
+ cd sipx
66
+ uv sync
67
+ ```
68
+
69
+ ### Requirements
70
+
71
+ Python 3.12+
72
+
73
+ ## Usage Examples
74
+
75
+ ### SIP Server (FastAPI-style decorators + DI)
76
+
77
+ ```python
78
+ from typing import Annotated
79
+ from sipx import SIPServer, Request, FromHeader, Header
80
+
81
+ server = SIPServer(local_host="127.0.0.1", local_port=5060)
82
+
83
+ @server.register
84
+ def on_register(request: Request, caller: Annotated[str, FromHeader]):
85
+ print(f"REGISTER from {caller}")
86
+ return request.ok()
87
+
88
+ @server.message
89
+ def on_message(request: Request, caller: Annotated[str, FromHeader]):
90
+ body = request.content.decode() if request.content else ""
91
+ print(f"MESSAGE from {caller}: {body}")
92
+ return request.ok()
93
+
94
+ server.start()
95
+ ```
96
+
97
+ ### Async Client
98
+
99
+ ```python
100
+ import asyncio
101
+ from sipx import AsyncClient
102
+
103
+ async def main():
104
+ async with AsyncClient(local_port=5061) as client:
105
+ client.auth = ("alice", "secret")
106
+
107
+ r = await client.register("sip:alice@pbx.com")
108
+ print(f"REGISTER: {r.status_code}")
109
+
110
+ sdp = client.create_sdp(port=8000)
111
+ r = await client.invite("sip:bob@pbx.com", body=sdp.to_string())
112
+ if r and r.status_code == 200:
113
+ await client.ack()
114
+ await asyncio.sleep(5)
115
+ await client.bye()
116
+
117
+ asyncio.run(main())
118
+ ```
119
+
120
+ ### FastAPI + SIP Integration
121
+
122
+ Run a REST API that controls SIP operations:
123
+
124
+ ```bash
125
+ # Terminal 1: SIP server
126
+ uv run python examples/server.py
127
+
128
+ # Terminal 2: FastAPI
129
+ uv run python examples/fastapi_sip.py
130
+
131
+ # Terminal 3: test via curl
132
+ curl -X POST http://localhost:8000/message \
133
+ -H "Content-Type: application/json" \
134
+ -d '{"uri":"sip:test@127.0.0.1:15090","content":"Hello from FastAPI!"}' | jq
135
+ ```
136
+
137
+ ```json
138
+ {
139
+ "status_code": 200,
140
+ "reason": "OK"
141
+ }
142
+ ```
143
+
144
+ Full request/response trace with retransmission timers, DI resolution, and FSM state transitions:
145
+
146
+ ```text
147
+ # FastAPI (client side)
148
+ INFO UDP bound to 0.0.0.0:40703
149
+ DEBUG Transaction 592b5fee created: MESSAGE (type=NON_INVITE)
150
+ DEBUG Timer E started (0.5s) # retransmission armed
151
+ DEBUG Timer F started (32.0s) # timeout armed
152
+ DEBUG UDP send 335 bytes -> 127.0.0.1:15090
153
+ DEBUG UDP recv 245 bytes <- 127.0.0.1:15090
154
+ DEBUG <<< 200 OK
155
+ DEBUG Transaction 592b5fee: TRYING -> COMPLETED
156
+ DEBUG Timer E cancelled # retransmission stopped
157
+ DEBUG Timer F cancelled # timeout stopped
158
+
159
+ # SIP Server (server side)
160
+ DEBUG UDP recv 335 bytes <- 127.0.0.1:40703
161
+ DEBUG Resolving handler on_message
162
+ DEBUG Extracting caller via FromHeader()
163
+ MESSAGE from <sip:user@0.0.0.0>: Hello from FastAPI!
164
+ DEBUG >>> SENDING 200 OK to 127.0.0.1:40703
165
+ DEBUG UDP send 245 bytes -> 127.0.0.1:40703
166
+ ```
167
+
168
+ ### Event Handlers
169
+
170
+ ```python
171
+ from sipx import Events, on
172
+
173
+ class CallEvents(Events):
174
+ @on('INVITE', status=200)
175
+ def on_call_ok(self, request, response, context):
176
+ print("Call accepted!")
177
+
178
+ @on('INVITE', status=(180, 183))
179
+ def on_ringing(self, request, response, context):
180
+ print(f"Ringing... ({response.status_code})")
181
+
182
+ @on(status=(401, 407))
183
+ def on_auth(self, request, response, context):
184
+ print("Auth challenge received")
185
+ ```
186
+
187
+ ### Authentication
188
+
189
+ ```python
190
+ # Tuple auth (auto-retry on 401/407)
191
+ client.auth = ("alice", "secret")
192
+
193
+ # Or explicit Auth object
194
+ client.auth = Auth.Digest("alice", "secret")
195
+
196
+ # Per-request override
197
+ r = client.register("sip:alice@pbx.com")
198
+ if r.status_code == 401:
199
+ r = client.retry_with_auth(r, auth=Auth.Digest("alice", "other"))
200
+ ```
201
+
202
+ ## Supported Methods
203
+
204
+ | Method | Description | Example |
205
+ | --------- | ---------------------- | ------------------------------------------------------------- |
206
+ | INVITE | Establish a call | `client.invite('sip:bob@ex.com', body=sdp)` |
207
+ | ACK | Acknowledge INVITE | `client.ack()` (auto dialog) |
208
+ | BYE | Terminate a call | `client.bye()` (auto dialog) |
209
+ | CANCEL | Cancel pending INVITE | `client.cancel(response=r)` |
210
+ | REGISTER | Register location | `client.register('sip:alice@ex.com')` |
211
+ | OPTIONS | Query capabilities | `client.options('sip:ex.com')` |
212
+ | MESSAGE | Instant message | `client.message('sip:bob@ex.com', content='Hi')` |
213
+ | SUBSCRIBE | Subscribe to events | `client.subscribe('sip:bob@ex.com')` |
214
+ | NOTIFY | Event notification | `client.notify('sip:bob@ex.com')` |
215
+ | REFER | Call transfer | `client.refer('sip:bob@ex.com', refer_to='sip:carol@ex.com')` |
216
+ | INFO | Mid-dialog info (DTMF) | `client.info('sip:bob@ex.com', content=dtmf)` |
217
+ | UPDATE | Update session | `client.update('sip:bob@ex.com', sdp_content=sdp)` |
218
+ | PRACK | Provisional ACK | `client.prack(response=r)` |
219
+ | PUBLISH | Publish state | `client.publish('sip:bob@ex.com')` |
220
+
221
+ ## RFC Compliance
222
+
223
+ | RFC | Title | Status |
224
+ | ---- | --------------------------------- | ------------------------------------------ |
225
+ | 3261 | SIP: Session Initiation Protocol | Complete (auto 100 Trying, retransmission) |
226
+ | 2617 | HTTP Digest Authentication | Complete (MD5, SHA-256) |
227
+ | 7616 | HTTP Digest (SHA-256) | Complete |
228
+ | 4566 | SDP: Session Description Protocol | Complete (ICE, SRTP, DTLS) |
229
+ | 3264 | Offer/Answer Model with SDP | Complete |
230
+ | 3550 | RTP: Real-time Transport Protocol | Complete (sync + async) |
231
+ | 4733 | DTMF via RTP (telephone-event) | Complete |
232
+ | 3263 | DNS SRV Resolution | Complete (auto in Client) |
233
+ | 4028 | Session Timers | Complete |
234
+
235
+ ## Documentation
236
+
237
+ | Document | Description |
238
+ | ---------------------------------------- | ------------------------------------------------------- |
239
+ | [docs/SDD.md](docs/SDD.md) | Software Design Document (full spec, diagrams, roadmap) |
240
+ | [examples/README.md](examples/README.md) | Examples catalog (22 examples) |
241
+ | [docker/asterisk/](docker/asterisk/) | Asterisk test environment |
242
+
243
+ ## Testing with Asterisk
244
+
245
+ ```bash
246
+ cd docker/asterisk
247
+ docker-compose up -d
248
+ uv run python examples/asterisk.py
249
+ ```
250
+
251
+ ## Examples
252
+
253
+ See [examples/README.md](examples/README.md) for the full list. Highlights:
254
+
255
+ | Example | What it shows |
256
+ | ----------------------------------------------------- | ---------------------------------------- |
257
+ | [quickstart.py](examples/quickstart.py) | One-liner register, options, call, send |
258
+ | [call.py](examples/call.py) | REGISTER -> INVITE -> ACK -> BYE |
259
+ | [server.py](examples/server.py) | SIPServer with decorators + DI |
260
+ | [ivr.py](examples/ivr.py) | Async IVR with RTP + DTMF |
261
+ | [response_builders.py](examples/response_builders.py) | request.ok/trying/error, dialog tracking |
262
+ | [asterisk.py](examples/asterisk.py) | Comprehensive integration test |
263
+
264
+ ## Roadmap
265
+
266
+ See [docs/SDD.md](docs/SDD.md) for the full roadmap. Key upcoming work:
267
+
268
+ - **SRTP** — packet encryption (RFC 3711)
269
+ - **RTCP** — control protocol (RFC 3550 Section 6)
270
+ - **Jitter buffer** — for smooth RTP playback
271
+ - **Coverage >80%** — currently 607 tests, ~60%
272
+ - **PyPI publishing** — package ready, CI pipeline configured
273
+
274
+ ## Development
275
+
276
+ ```bash
277
+ git clone https://github.com/yourusername/sipx.git
278
+ cd sipx
279
+ uv sync
280
+ uv run ruff check sipx
281
+ uv run pytest
282
+ ```
283
+
284
+ ## Inspirations
285
+
286
+ API design:
287
+
288
+ - [httpx](https://www.python-httpx.org/) — Pythonic API, sync/async, extensible
289
+
290
+ Reference implementations:
291
+
292
+ - [sipd](https://github.com/initbar/sipd) — SIP daemon implementation
293
+ - [sipmessage](https://github.com/spacinov/sipmessage) — SIP message parsing
294
+ - [sip-parser](https://github.com/alxgb/sip-parser) — SIP parser
295
+ - [PySipIvr](https://github.com/ersansrck/PySipIvr) — SIP IVR implementation
296
+ - [sip-resources](https://github.com/miconda/sip-resources) — Curated SIP resources and documentation
297
+
298
+ Other inspirations:
299
+
300
+ - [pyVoIP](https://github.com/tayler6000/pyVoIP) — VoIP functionality
301
+ - [aiosip](https://github.com/Eyepea/aiosip) — Async SIP
302
+ - [b2bua](https://github.com/sippy/b2bua) — Back-to-back user agent
303
+ - [pysipp](https://github.com/SIPp/pysipp) — SIP testing
304
+ - [PySIPio](https://pypi.org/project/PySIPio/) — SIP I/O
305
+ - [katariSIP](https://github.com/klocation/katarisip) — SIP library
306
+ - [SIP-Auth-helper](https://github.com/pbertera/SIP-Auth-helper) — SIP auth tools
307
+ - [callsip.py](https://github.com/rundekugel/callsip.py) — Simple SIP caller
308
+
309
+ ## License
310
+
311
+ MIT License
@@ -0,0 +1,55 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "sipx"
7
+ dynamic = ["version"]
8
+ description = "Add your description here"
9
+ readme = "README.md"
10
+ requires-python = ">=3.13"
11
+ dependencies = ["rich>=14.1.0"]
12
+
13
+ [project.scripts]
14
+ sipx = "sipx.main:main"
15
+
16
+ [dependency-groups]
17
+ cli = ["typer>=0.17.4"]
18
+ dev = [
19
+ "fastapi[standard]>=0.135.2",
20
+ "pre-commit>=4.3.0",
21
+ "ruff>=0.12.12",
22
+ "taskipy>=1.14.1",
23
+ "ty>=0.0.1a20",
24
+ ]
25
+ test = ["pytest>=8.4.2", "pytest-asyncio>=1.1.0", "pytest-cov>=6.3.0"]
26
+
27
+ [tool.taskipy.tasks]
28
+ # Quality
29
+ lint = "ruff check sipx/ tests/ examples/"
30
+ format = "ruff format sipx/ tests/ examples/"
31
+ check = "ruff format --check sipx/ tests/ examples/ && ruff check sipx/ tests/ examples/"
32
+ typecheck = "ty check"
33
+
34
+ # Test
35
+ test = "pytest tests/ -v"
36
+ test-asterisk = "ASTERISK_INTEGRATION=1 pytest tests/integration/test_asterisk.py -v"
37
+ cov = "pytest tests/ --cov=sipx --cov-report=term-missing"
38
+ cov-html = "pytest tests/ --cov=sipx --cov-report=html && open htmlcov/index.html"
39
+
40
+ # CI (run in order)
41
+ ci = "task check && task test"
42
+
43
+ [tool.setuptools]
44
+ package-dir = { "" = "." }
45
+
46
+ [tool.setuptools.dynamic]
47
+ version = { attr = "sipx._version.__version__" }
48
+
49
+ [tool.setuptools.packages.find]
50
+ where = ["."]
51
+ include = ["sipx*"]
52
+ exclude = ["docker*", "docs*", "examples*", "tests*"]
53
+
54
+ [tool.setuptools.package-data]
55
+ sipx = ["py.typed"]
sipx-0.0.4/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+