wirexaapi 1.0.0__py3-none-any.whl

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.
wirexaapi/__init__.py ADDED
@@ -0,0 +1,187 @@
1
+ """
2
+ Wirexa API SDK - Drop-in replacement del SDK de SignalWire/Twilio.
3
+
4
+ CREDENCIALES (las obtienes del dashboard):
5
+ space_url = "your-space.example.com:5000"
6
+ project_id = "your-project-id"
7
+ api_token = "your-api-token"
8
+
9
+ USO:
10
+
11
+ ANTES (SignalWire original):
12
+ from signalwire.rest import Client
13
+ client = Client(project_id, api_token, signalwire_space_url=space_url)
14
+
15
+ DESPUÉS (Wirexa API):
16
+ from wirexaapi import Client
17
+ client = Client(project_id, api_token, signalwire_space_url=space_url)
18
+
19
+ El resto del código NO cambia.
20
+ """
21
+ from __future__ import annotations
22
+
23
+ import logging
24
+ import requests
25
+ from typing import Optional
26
+
27
+ logger = logging.getLogger("wirexaapi.http_client")
28
+
29
+
30
+ class _CallsResource:
31
+ """Imita el objeto client.calls del SDK."""
32
+
33
+ def __init__(self, base_url: str, auth: tuple):
34
+ self._base = base_url
35
+ self._auth = auth
36
+
37
+ def create(
38
+ self,
39
+ to: str,
40
+ from_: str,
41
+ url: str,
42
+ method: str = "POST",
43
+ timeout: int = 28,
44
+ record: bool = False,
45
+ status_callback: Optional[str] = None,
46
+ status_callback_method: str = "POST",
47
+ status_callback_event: Optional[list] = None,
48
+ recording_status_callback: Optional[str] = None,
49
+ recording_status_callback_method: str = "POST",
50
+ machine_detection: Optional[str] = None,
51
+ machine_detection_timeout: int = 30,
52
+ async_amd: bool = False,
53
+ async_amd_status_callback: Optional[str] = None,
54
+ ) -> "_CallInstance":
55
+ data = {
56
+ "To": to,
57
+ "From": from_,
58
+ "Url": url,
59
+ "Method": method,
60
+ "Timeout": str(timeout),
61
+ "Record": "true" if record else "false",
62
+ "StatusCallbackMethod": status_callback_method,
63
+ "RecordingStatusCallbackMethod": recording_status_callback_method,
64
+ }
65
+
66
+ if status_callback:
67
+ data["StatusCallback"] = status_callback
68
+
69
+ if status_callback_event:
70
+ data["StatusCallbackEvent"] = " ".join(status_callback_event)
71
+
72
+ if recording_status_callback:
73
+ data["RecordingStatusCallback"] = recording_status_callback
74
+
75
+ if machine_detection:
76
+ data["MachineDetection"] = machine_detection
77
+ data["MachineDetectionTimeout"] = str(machine_detection_timeout)
78
+
79
+ if async_amd:
80
+ data["AsyncAmd"] = "true"
81
+ if async_amd_status_callback:
82
+ data["AsyncAmdStatusCallback"] = async_amd_status_callback
83
+
84
+ endpoint = f"{self._base}/Calls.json"
85
+
86
+ logger.info("Request: POST %s", endpoint)
87
+ logger.info("Params: %s", data)
88
+
89
+ r = requests.post(endpoint, auth=self._auth, data=data, timeout=30)
90
+
91
+ logger.info("Response: HTTP %s", r.status_code)
92
+ logger.info("Response Body: %s", r.text[:500])
93
+
94
+ r.raise_for_status()
95
+ instance = _CallInstance(r.json())
96
+
97
+ logger.info("[calls.create] Call %s status: %s", instance.sid, instance.status)
98
+
99
+ return instance
100
+
101
+ def list(self, status: Optional[str] = None, limit: int = 50) -> list:
102
+ params = {"PageSize": limit}
103
+ if status:
104
+ params["Status"] = status
105
+ r = requests.get(f"{self._base}/Calls.json", auth=self._auth, params=params, timeout=10)
106
+ r.raise_for_status()
107
+ return [_CallInstance(c) for c in r.json().get("calls", [])]
108
+
109
+ def __call__(self, call_sid: str) -> "_CallContext":
110
+ return _CallContext(self._base, self._auth, call_sid)
111
+
112
+
113
+ class _CallContext:
114
+ """Imita client.calls('CAxxxxx')"""
115
+
116
+ def __init__(self, base_url: str, auth: tuple, call_sid: str):
117
+ self._base = base_url
118
+ self._auth = auth
119
+ self._sid = call_sid
120
+
121
+ def fetch(self) -> "_CallInstance":
122
+ r = requests.get(f"{self._base}/Calls/{self._sid}.json", auth=self._auth, timeout=10)
123
+ r.raise_for_status()
124
+ return _CallInstance(r.json())
125
+
126
+ def update(self, status: Optional[str] = None, url: Optional[str] = None) -> "_CallInstance":
127
+ data = {}
128
+ if status:
129
+ data["Status"] = status
130
+ if url:
131
+ data["Url"] = url
132
+ r = requests.post(f"{self._base}/Calls/{self._sid}.json", auth=self._auth, data=data, timeout=10)
133
+ r.raise_for_status()
134
+ return _CallInstance(r.json())
135
+
136
+
137
+ class _CallInstance:
138
+ """Objeto retornado por calls.create() / fetch()."""
139
+
140
+ def __init__(self, data: dict):
141
+ self._data = data
142
+ self.sid = data.get("sid", "")
143
+ self.status = data.get("status", "")
144
+ self.to = data.get("to", "")
145
+ self.from_ = data.get("from", "")
146
+ self.direction = data.get("direction", "")
147
+ self.duration = data.get("duration")
148
+ self.answered_by = data.get("answered_by")
149
+ self.date_created = data.get("date_created")
150
+
151
+ def __repr__(self):
152
+ return f"<Call sid={self.sid!r} status={self.status!r}>"
153
+
154
+
155
+ class Client:
156
+ """Cliente principal para Wirexa API.
157
+
158
+ Uso:
159
+ client = Client(
160
+ project_id,
161
+ api_token,
162
+ signalwire_space_url="your-space.example.com:5000"
163
+ )
164
+ call = client.calls.create(to=..., from_=..., url=...)
165
+ """
166
+
167
+ def __init__(
168
+ self,
169
+ project_id: str,
170
+ api_token: str,
171
+ signalwire_space_url: str = "localhost:5000",
172
+ ):
173
+ self.project_id = project_id
174
+ self.api_token = api_token
175
+ self.space_url = signalwire_space_url
176
+
177
+ protocol = "https" if "." in signalwire_space_url and ":" not in signalwire_space_url else "http"
178
+ self._base_url = (
179
+ f"{protocol}://{signalwire_space_url}"
180
+ f"/api/laml/2010-04-01/Accounts/{project_id}"
181
+ )
182
+
183
+ self._auth = (project_id, api_token)
184
+ self.calls = _CallsResource(self._base_url, self._auth)
185
+
186
+ def __repr__(self):
187
+ return f"<WirexaClient project={self.project_id!r} space={self.space_url!r}>"
@@ -0,0 +1,86 @@
1
+ Metadata-Version: 2.4
2
+ Name: wirexaapi
3
+ Version: 1.0.0
4
+ Summary: Wirexa API SDK - SignalWire/Twilio compatible voice API client
5
+ Author-email: Wirexa Team <dev@wirexa.io>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/wirexa/wirexa-api
8
+ Project-URL: Repository, https://github.com/wirexa/wirexa-api
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.8
14
+ Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Description-Content-Type: text/markdown
19
+ Requires-Dist: requests>=2.25.0
20
+ Provides-Extra: dev
21
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
22
+
23
+ # Wirexa API SDK
24
+
25
+ A production-ready Python SDK for Wirexa API - a self-hosted telephony platform compatible with SignalWire and Twilio REST API.
26
+
27
+ ## Installation
28
+
29
+ ```bash
30
+ pip install wirexaapi
31
+ ```
32
+
33
+ ## Quick Start
34
+
35
+ ```python
36
+ from wirexaapi import Client
37
+
38
+ # Connect to your Wirexa API instance
39
+ client = Client(
40
+ project_id="your-project-id",
41
+ api_token="your-api-token",
42
+ signalwire_space_url="your-space.example.com:5000"
43
+ )
44
+
45
+ # Make a call
46
+ call = client.calls.create(
47
+ to="+573001234567",
48
+ from_="+18005550100",
49
+ url="https://your-backend.com/xml",
50
+ method="POST",
51
+ timeout=28,
52
+ record=True,
53
+ status_callback="https://your-backend.com/status",
54
+ status_callback_event=["initiated", "ringing", "completed"],
55
+ )
56
+
57
+ print(f"Call SID: {call.sid}")
58
+ print(f"Call Status: {call.status}")
59
+ ```
60
+
61
+ ## Migration from SignalWire
62
+
63
+ If you're migrating from SignalWire SDK:
64
+
65
+ ```python
66
+ # Before
67
+ from signalwire.rest import Client as SignalWireClient
68
+
69
+ # After
70
+ from wirexaapi import Client as SignalWireClient
71
+ ```
72
+
73
+ The rest of your code stays the same!
74
+
75
+ ## Features
76
+
77
+ - Full SignalWire/Twilio API compatibility
78
+ - Call creation, retrieval, and listing
79
+ - Machine Detection (AMD)
80
+ - Recording
81
+ - Status callbacks
82
+ - Async AMD support
83
+
84
+ ## License
85
+
86
+ MIT
@@ -0,0 +1,5 @@
1
+ wirexaapi/__init__.py,sha256=GGPzaqAvDSp5t3woK9IY-HmxEMHhKVz2CvNVJwnUOZY,6002
2
+ wirexaapi-1.0.0.dist-info/METADATA,sha256=Wo1BpzXryhB8fgg6gAdb3Qgh6yQykbTS3Ou6SqTbsiQ,2132
3
+ wirexaapi-1.0.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
4
+ wirexaapi-1.0.0.dist-info/top_level.txt,sha256=lWV0OOIgp8Kaq1EdbOFLYcf6JVwfnAaWhE0g3lNg5c0,10
5
+ wirexaapi-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ wirexaapi