wirexaapi 1.1.0__tar.gz → 1.1.1__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.
- {wirexaapi-1.1.0 → wirexaapi-1.1.1}/PKG-INFO +1 -1
- {wirexaapi-1.1.0 → wirexaapi-1.1.1}/pyproject.toml +1 -1
- {wirexaapi-1.1.0 → wirexaapi-1.1.1}/wirexaapi/db.py +64 -19
- {wirexaapi-1.1.0 → wirexaapi-1.1.1}/wirexaapi.egg-info/PKG-INFO +1 -1
- {wirexaapi-1.1.0 → wirexaapi-1.1.1}/README.md +0 -0
- {wirexaapi-1.1.0 → wirexaapi-1.1.1}/setup.cfg +0 -0
- {wirexaapi-1.1.0 → wirexaapi-1.1.1}/wirexaapi/__init__.py +0 -0
- {wirexaapi-1.1.0 → wirexaapi-1.1.1}/wirexaapi/rest/__init__.py +0 -0
- {wirexaapi-1.1.0 → wirexaapi-1.1.1}/wirexaapi.egg-info/SOURCES.txt +0 -0
- {wirexaapi-1.1.0 → wirexaapi-1.1.1}/wirexaapi.egg-info/dependency_links.txt +0 -0
- {wirexaapi-1.1.0 → wirexaapi-1.1.1}/wirexaapi.egg-info/requires.txt +0 -0
- {wirexaapi-1.1.0 → wirexaapi-1.1.1}/wirexaapi.egg-info/top_level.txt +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "wirexaapi"
|
|
7
|
-
version = "1.1.
|
|
7
|
+
version = "1.1.1"
|
|
8
8
|
description = "Wirexa API SDK - voice API client (SignalWire/Twilio compatible) and Wirexa DB document database client"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = {text = "MIT"}
|
|
@@ -307,43 +307,76 @@ class WirexaClient:
|
|
|
307
307
|
with self._lock:
|
|
308
308
|
self._all.append(c)
|
|
309
309
|
if c.sock is None:
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
except socket.timeout as e:
|
|
313
|
-
raise ServerSelectionTimeoutError("%s:%d: %s" % (self.address + (e,)))
|
|
314
|
-
except OSError as e:
|
|
315
|
-
raise ConnectionFailure("%s:%d: %s" % (self.address + (e,)))
|
|
310
|
+
c.connect() # las excepciones las gestiona _op (reintenta)
|
|
311
|
+
c._wdb_used = False
|
|
316
312
|
c.sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
|
|
313
|
+
try:
|
|
314
|
+
c.sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
|
|
315
|
+
except OSError:
|
|
316
|
+
pass
|
|
317
317
|
c.sock.settimeout(self._read_timeout)
|
|
318
318
|
return c
|
|
319
319
|
|
|
320
|
+
# Errores que significan "la conexión keep-alive ya no sirve": hay que
|
|
321
|
+
# reabrir y reintentar. Incluye los de TLS (SSLEOFError/"EOF occurred in
|
|
322
|
+
# violation of protocol"), que es lo que lanza Python cuando nginx cierra
|
|
323
|
+
# una conexión keep-alive inactiva (keepalive_timeout) y se reusa el socket.
|
|
324
|
+
_STALE = (
|
|
325
|
+
http.client.RemoteDisconnected,
|
|
326
|
+
http.client.BadStatusLine,
|
|
327
|
+
http.client.CannotSendRequest,
|
|
328
|
+
http.client.ResponseNotReady,
|
|
329
|
+
ConnectionResetError,
|
|
330
|
+
BrokenPipeError,
|
|
331
|
+
ConnectionAbortedError,
|
|
332
|
+
ssl.SSLError, # incluye ssl.SSLEOFError y ssl.SSLZeroReturnError
|
|
333
|
+
)
|
|
334
|
+
|
|
320
335
|
def _op(self, op, retry_safe=False, **fields):
|
|
321
336
|
if self._closed:
|
|
322
337
|
raise ConnectionFailure("el cliente está cerrado")
|
|
323
338
|
body = json.dumps(_encode(dict(op=op, **fields)), separators=(",", ":")).encode()
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
339
|
+
attempts = 4
|
|
340
|
+
last = None
|
|
341
|
+
for attempt in range(attempts):
|
|
342
|
+
c = None
|
|
343
|
+
reused = False
|
|
344
|
+
stage = "connect"
|
|
327
345
|
try:
|
|
346
|
+
c = self._conn()
|
|
347
|
+
reused = getattr(c, "_wdb_used", False)
|
|
348
|
+
stage = "request"
|
|
328
349
|
c.request("POST", _PATH, body, self._headers)
|
|
329
350
|
r = c.getresponse()
|
|
330
351
|
status, raw = r.status, r.read()
|
|
331
352
|
c._wdb_used = True
|
|
332
353
|
break
|
|
333
|
-
except (http.client.RemoteDisconnected, ConnectionResetError, BrokenPipeError,
|
|
334
|
-
http.client.CannotSendRequest, http.client.ResponseNotReady) as e:
|
|
335
|
-
# Conexión keep-alive cerrada por el servidor mientras estaba
|
|
336
|
-
# ociosa: se reabre y se reintenta una sola vez.
|
|
337
|
-
c.close()
|
|
338
|
-
c._wdb_used = False
|
|
339
|
-
if attempt == 1 or not (reused or retry_safe):
|
|
340
|
-
raise ConnectionFailure("%s:%d: %s" % (self.address + (e,)))
|
|
341
354
|
except socket.timeout as e:
|
|
342
|
-
|
|
355
|
+
last = e
|
|
356
|
+
self._kill(c)
|
|
357
|
+
# timeout al conectar -> reintentar; al leer -> la petición pudo
|
|
358
|
+
# ejecutarse, así que solo se reintenta si es idempotente.
|
|
359
|
+
if attempt + 1 < attempts and (stage == "connect" or retry_safe):
|
|
360
|
+
continue
|
|
361
|
+
if stage == "connect":
|
|
362
|
+
raise ServerSelectionTimeoutError("%s:%d: %s" % (self.address + (e,)))
|
|
343
363
|
raise ConnectionFailure("timeout esperando respuesta: %s" % e)
|
|
364
|
+
except self._STALE as e:
|
|
365
|
+
last = e
|
|
366
|
+
self._kill(c)
|
|
367
|
+
# Seguro reintentar cuando: falló al conectar (nunca se envió),
|
|
368
|
+
# el socket reusado estaba muerto (no se procesó), o es lectura.
|
|
369
|
+
if attempt + 1 < attempts and (stage == "connect" or reused or retry_safe):
|
|
370
|
+
continue
|
|
371
|
+
raise ConnectionFailure("%s:%d: %s" % (self.address + (e,)))
|
|
344
372
|
except OSError as e:
|
|
345
|
-
|
|
373
|
+
last = e
|
|
374
|
+
self._kill(c)
|
|
375
|
+
if attempt + 1 < attempts and (stage == "connect" or reused or retry_safe):
|
|
376
|
+
continue
|
|
346
377
|
raise ConnectionFailure("%s:%d: %s" % (self.address + (e,)))
|
|
378
|
+
else: # pragma: no cover
|
|
379
|
+
raise ConnectionFailure("%s:%d: %s" % (self.address + (last,)))
|
|
347
380
|
try:
|
|
348
381
|
data = json.loads(raw, object_hook=_decode_hook)
|
|
349
382
|
except ValueError:
|
|
@@ -356,6 +389,18 @@ class WirexaClient:
|
|
|
356
389
|
raise OperationFailure(msg, code, data)
|
|
357
390
|
return data
|
|
358
391
|
|
|
392
|
+
def _kill(self, c):
|
|
393
|
+
"""Cierra la conexión del hilo para que el próximo _op abra una nueva."""
|
|
394
|
+
if c is None:
|
|
395
|
+
return
|
|
396
|
+
try:
|
|
397
|
+
c.close()
|
|
398
|
+
except Exception:
|
|
399
|
+
pass
|
|
400
|
+
c._wdb_used = False
|
|
401
|
+
if getattr(self._local, "conn", None) is c:
|
|
402
|
+
self._local.conn = None
|
|
403
|
+
|
|
359
404
|
def __getitem__(self, name):
|
|
360
405
|
return Database(self, name)
|
|
361
406
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|