Compare commits
9 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
94b5ce7ed3 | ||
|
|
2c1e2ed373 | ||
|
|
c80a2db665 | ||
|
|
1748e836bc | ||
|
|
7dca8f9ba4 | ||
|
|
8337e7bae6 | ||
|
|
a5f57a7285 | ||
|
|
8bd6eebe9d | ||
|
|
b288d89c15 |
7 changed files with 78 additions and 51 deletions
|
|
@ -1,3 +1,7 @@
|
||||||
|
0.5.6
|
||||||
|
-----
|
||||||
|
- Backported to support requests 0.8.2
|
||||||
|
|
||||||
0.5.5
|
0.5.5
|
||||||
-----
|
-----
|
||||||
- Fixed reconnection in the event of server restart
|
- Fixed reconnection in the event of server restart
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,8 @@ socketIO-client
|
||||||
===============
|
===============
|
||||||
Here is a `socket.io <http://socket.io>`_ client library for Python. You can use it to write test code for your socket.io server.
|
Here is a `socket.io <http://socket.io>`_ client library for Python. You can use it to write test code for your socket.io server.
|
||||||
|
|
||||||
|
Please note that this version implements `socket.io protocol 0.9 <https://github.com/learnboost/socket.io-spec>`_, which is compatible with `gevent-socketio <https://github.com/abourget/gevent-socketio>`_. If you want to communicate using `socket.io protocol 1.x <https://github.com/automattic/socket.io-protocol>`_, please use `socketIO-client 0.6.3 <https://pypi.python.org/pypi/socketIO-client/0.6.3>`_ or higher.
|
||||||
|
|
||||||
|
|
||||||
Installation
|
Installation
|
||||||
------------
|
------------
|
||||||
|
|
@ -32,6 +34,7 @@ Activate isolated environment. ::
|
||||||
|
|
||||||
Launch your socket.io server. ::
|
Launch your socket.io server. ::
|
||||||
|
|
||||||
|
npm install -g socket.io@0.9
|
||||||
node serve-tests.js
|
node serve-tests.js
|
||||||
|
|
||||||
For debugging information, run these commands first. ::
|
For debugging information, run these commands first. ::
|
||||||
|
|
@ -156,5 +159,5 @@ Credits
|
||||||
- `Alexandre Bourget <https://github.com/abourget>`_ wrote `gevent-socketio <https://github.com/abourget/gevent-socketio>`_, which is a socket.io server written in Python.
|
- `Alexandre Bourget <https://github.com/abourget>`_ wrote `gevent-socketio <https://github.com/abourget/gevent-socketio>`_, which is a socket.io server written in Python.
|
||||||
- `Paul Kienzle <https://github.com/pkienzle>`_, `Zac Lee <https://github.com/zratic>`_, `Josh VanderLinden <https://github.com/codekoala>`_, `Ian Fitzpatrick <https://github.com/ifitzpatrick>`_, `Lucas Klein <https://github.com/lukasklein>`_, `Rui Chicoria <https://github.com/rchicoria>`_, `Travis Odom <https://github.com/burstaholic>`_, `Patrick Huber <https://github.com/stackmagic>`_, `Brad Campbell <https://github.com/bradjc>`_, `Daniel <https://github.com/dabidan>`_, `Sean Arietta <https://github.com/sarietta>`_ submitted code to expand support of the socket.io protocol.
|
- `Paul Kienzle <https://github.com/pkienzle>`_, `Zac Lee <https://github.com/zratic>`_, `Josh VanderLinden <https://github.com/codekoala>`_, `Ian Fitzpatrick <https://github.com/ifitzpatrick>`_, `Lucas Klein <https://github.com/lukasklein>`_, `Rui Chicoria <https://github.com/rchicoria>`_, `Travis Odom <https://github.com/burstaholic>`_, `Patrick Huber <https://github.com/stackmagic>`_, `Brad Campbell <https://github.com/bradjc>`_, `Daniel <https://github.com/dabidan>`_, `Sean Arietta <https://github.com/sarietta>`_ submitted code to expand support of the socket.io protocol.
|
||||||
- `Bernard Pratz <https://github.com/guyzmo>`_, `Francis Bull <https://github.com/franbull>`_ wrote prototypes to support xhr-polling and jsonp-polling.
|
- `Bernard Pratz <https://github.com/guyzmo>`_, `Francis Bull <https://github.com/franbull>`_ wrote prototypes to support xhr-polling and jsonp-polling.
|
||||||
- `Eric Chen <https://github.com/taiyangc>`_, `Denis Zinevich <https://github.com/dzinevich>`_, `Thiago Hersan <https://github.com/thiagohersan>`_, `Nayef Copty <https://github.com/nayefc>`_, `Jörgen Karlsson <https://github.com/jorgen-k>`_, `Branden Ghena <https://github.com/brghena>`_ suggested ways to make the connection more robust.
|
- `Eric Chen <https://github.com/taiyangc>`_, `Denis Zinevich <https://github.com/dzinevich>`_, `Thiago Hersan <https://github.com/thiagohersan>`_, `Nayef Copty <https://github.com/nayefc>`_, `Jörgen Karlsson <https://github.com/jorgen-k>`_, `Branden Ghena <https://github.com/brghena>`_, `Tim Landscheidt <https://github.com/scfc>`_ suggested ways to make the connection more robust.
|
||||||
- `Merlijn van Deen <https://github.com/valhallasw>`_, `Frederic Sureau <https://github.com/fredericsureau>`_, `Marcus Cobden <https://github.com/leth>`_, `Drew Hutchison <https://github.com/drewhutchison>`_, `wuurrd <https://github.com/wuurrd>`_, `Adam Kecer <https://github.com/amfg>`_, `Alex Monk <https://github.com/Krenair>`_, `Vishal P R <https://github.com/vishalwy>`_, `John Vandenberg <https://github.com/jayvdb>`_, `Thomas Grainger <https://github.com/graingert>`_ proposed changes that make the library more friendly and practical for you!
|
- `Merlijn van Deen <https://github.com/valhallasw>`_, `Frederic Sureau <https://github.com/fredericsureau>`_, `Marcus Cobden <https://github.com/leth>`_, `Drew Hutchison <https://github.com/drewhutchison>`_, `wuurrd <https://github.com/wuurrd>`_, `Adam Kecer <https://github.com/amfg>`_, `Alex Monk <https://github.com/Krenair>`_, `Vishal P R <https://github.com/vishalwy>`_, `John Vandenberg <https://github.com/jayvdb>`_, `Thomas Grainger <https://github.com/graingert>`_ proposed changes that make the library more friendly and practical for you!
|
||||||
|
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
Release 0.6.1 #41 #52
|
|
||||||
+ Update serve-tests.js
|
|
||||||
+ Integrate serve-tests.js changes from sarietta
|
|
||||||
= Put tests in index.html
|
|
||||||
Update tests
|
|
||||||
Merge sarietta's pull request
|
|
||||||
Revive heartbeat as separate process
|
|
||||||
Implement rooms #65
|
|
||||||
2
setup.py
2
setup.py
|
|
@ -9,7 +9,7 @@ CHANGES = open(os.path.join(here, 'CHANGES.rst')).read()
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
name='socketIO-client',
|
name='socketIO-client',
|
||||||
version='0.5.5',
|
version='0.5.6',
|
||||||
description='A socket.io client library',
|
description='A socket.io client library',
|
||||||
long_description=README + '\n\n' + CHANGES,
|
long_description=README + '\n\n' + CHANGES,
|
||||||
license='MIT',
|
license='MIT',
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ except ImportError:
|
||||||
|
|
||||||
from .exceptions import (
|
from .exceptions import (
|
||||||
SocketIOError, ConnectionError, TimeoutError, PacketError)
|
SocketIOError, ConnectionError, TimeoutError, PacketError)
|
||||||
|
from .symmetries import _get_text
|
||||||
from .transports import (
|
from .transports import (
|
||||||
_get_response, TRANSPORTS,
|
_get_response, TRANSPORTS,
|
||||||
_WebsocketTransport, _XHR_PollingTransport, _JSONP_PollingTransport)
|
_WebsocketTransport, _XHR_PollingTransport, _JSONP_PollingTransport)
|
||||||
|
|
@ -78,7 +79,7 @@ class BaseNamespace(object):
|
||||||
"""
|
"""
|
||||||
callback, args = find_callback(args)
|
callback, args = find_callback(args)
|
||||||
if callback:
|
if callback:
|
||||||
callback(*args)
|
callback(args)
|
||||||
|
|
||||||
def on_error(self, reason, advice):
|
def on_error(self, reason, advice):
|
||||||
'Called after server sends an error; you can override this method'
|
'Called after server sends an error; you can override this method'
|
||||||
|
|
@ -116,7 +117,7 @@ class BaseNamespace(object):
|
||||||
return getattr(
|
return getattr(
|
||||||
self,
|
self,
|
||||||
'on_' + event.replace(' ', '_'),
|
'on_' + event.replace(' ', '_'),
|
||||||
lambda *args: self.on_event(event, *args))
|
lambda *args: self.on_event(event, args))
|
||||||
|
|
||||||
|
|
||||||
class LoggingNamespace(BaseNamespace):
|
class LoggingNamespace(BaseNamespace):
|
||||||
|
|
@ -147,7 +148,7 @@ class LoggingNamespace(BaseNamespace):
|
||||||
arguments.append('callback(*args)')
|
arguments.append('callback(*args)')
|
||||||
self._log(logging.INFO, '%s [event] %s(%s)', self.path, event,
|
self._log(logging.INFO, '%s [event] %s(%s)', self.path, event,
|
||||||
', '.join(arguments))
|
', '.join(arguments))
|
||||||
super(LoggingNamespace, self).on_event(event, *args)
|
super(LoggingNamespace, self).on_event(event, args)
|
||||||
|
|
||||||
def on_error(self, reason, advice):
|
def on_error(self, reason, advice):
|
||||||
self._log(logging.INFO, '%s [error] %s', self.path, advice)
|
self._log(logging.INFO, '%s [error] %s', self.path, advice)
|
||||||
|
|
@ -236,7 +237,7 @@ class SocketIO(object):
|
||||||
callback, args = find_callback(args, kw)
|
callback, args = find_callback(args, kw)
|
||||||
self._transport.emit(path, event, args, callback)
|
self._transport.emit(path, event, args, callback)
|
||||||
|
|
||||||
def wait(self, seconds=None, for_callbacks=False):
|
def wait(self, seconds=None, for_callbacks=False, on_error=None, error_args=None):
|
||||||
"""Wait in a loop and process events as defined in the namespaces.
|
"""Wait in a loop and process events as defined in the namespaces.
|
||||||
|
|
||||||
- Omit seconds, i.e. call wait() without arguments, to wait forever.
|
- Omit seconds, i.e. call wait() without arguments, to wait forever.
|
||||||
|
|
@ -253,6 +254,8 @@ class SocketIO(object):
|
||||||
pass
|
pass
|
||||||
next(self._heartbeat_pacemaker)
|
next(self._heartbeat_pacemaker)
|
||||||
except ConnectionError as e:
|
except ConnectionError as e:
|
||||||
|
if on_error is not None and callable(on_error):
|
||||||
|
on_error(e, error_args)
|
||||||
try:
|
try:
|
||||||
warning = Exception('[connection error] %s' % e)
|
warning = Exception('[connection error] %s' % e)
|
||||||
warning_screen.throw(warning)
|
warning_screen.throw(warning)
|
||||||
|
|
@ -273,8 +276,8 @@ class SocketIO(object):
|
||||||
|
|
||||||
def _process_packet(self, packet):
|
def _process_packet(self, packet):
|
||||||
code, packet_id, path, data = packet
|
code, packet_id, path, data = packet
|
||||||
namespace = self.get_namespace(path)
|
namespace = self.get_namespace(path or '')
|
||||||
delegate = self._get_delegate(code)
|
delegate = self._get_delegate(code, packet)
|
||||||
delegate(packet, namespace._find_event_callback)
|
delegate(packet, namespace._find_event_callback)
|
||||||
|
|
||||||
def _stop_waiting(self, for_callbacks):
|
def _stop_waiting(self, for_callbacks):
|
||||||
|
|
@ -314,7 +317,7 @@ class SocketIO(object):
|
||||||
try:
|
try:
|
||||||
if self.connected:
|
if self.connected:
|
||||||
return self.__transport
|
return self.__transport
|
||||||
except AttributeError:
|
except AttributeError as e:
|
||||||
pass
|
pass
|
||||||
socketIO_session = self._get_socketIO_session()
|
socketIO_session = self._get_socketIO_session()
|
||||||
supported_transports = self._get_supported_transports(socketIO_session)
|
supported_transports = self._get_supported_transports(socketIO_session)
|
||||||
|
|
@ -394,7 +397,7 @@ class SocketIO(object):
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise PacketError('unhandled namespace path (%s)' % path)
|
raise PacketError('unhandled namespace path (%s)' % path)
|
||||||
|
|
||||||
def _get_delegate(self, code):
|
def _get_delegate(self, code, packet):
|
||||||
try:
|
try:
|
||||||
return {
|
return {
|
||||||
'0': self._on_disconnect,
|
'0': self._on_disconnect,
|
||||||
|
|
@ -406,9 +409,10 @@ class SocketIO(object):
|
||||||
'6': self._on_ack,
|
'6': self._on_ack,
|
||||||
'7': self._on_error,
|
'7': self._on_error,
|
||||||
'8': self._on_noop,
|
'8': self._on_noop,
|
||||||
|
'': self._on_noop
|
||||||
}[code]
|
}[code]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise PacketError('unexpected code (%s)' % code)
|
raise PacketError('unexpected code ({}): {}'.format([code], packet))
|
||||||
|
|
||||||
def _on_disconnect(self, packet, find_event_callback):
|
def _on_disconnect(self, packet, find_event_callback):
|
||||||
find_event_callback('disconnect')()
|
find_event_callback('disconnect')()
|
||||||
|
|
@ -421,26 +425,27 @@ class SocketIO(object):
|
||||||
|
|
||||||
def _on_message(self, packet, find_event_callback):
|
def _on_message(self, packet, find_event_callback):
|
||||||
code, packet_id, path, data = packet
|
code, packet_id, path, data = packet
|
||||||
args = [data]
|
args = data
|
||||||
|
self._send_args('message', args, path, packet_id, find_event_callback)
|
||||||
|
|
||||||
|
def _send_args(self, event, args, path, packet_id, find_event_callback):
|
||||||
|
ev_args = [args]
|
||||||
if packet_id:
|
if packet_id:
|
||||||
args.append(self._prepare_to_send_ack(path, packet_id))
|
ack = self._prepare_to_send_ack(path, packet_id)
|
||||||
find_event_callback('message')(*args)
|
ev_args.append(ack)
|
||||||
|
find_event_callback(event)(*ev_args)
|
||||||
|
|
||||||
def _on_json(self, packet, find_event_callback):
|
def _on_json(self, packet, find_event_callback):
|
||||||
code, packet_id, path, data = packet
|
code, packet_id, path, data = packet
|
||||||
args = [json.loads(data)]
|
args = json.loads(data)
|
||||||
if packet_id:
|
self._send_args('message', args, path, packet_id, find_event_callback)
|
||||||
args.append(self._prepare_to_send_ack(path, packet_id))
|
|
||||||
find_event_callback('message')(*args)
|
|
||||||
|
|
||||||
def _on_event(self, packet, find_event_callback):
|
def _on_event(self, packet, find_event_callback):
|
||||||
code, packet_id, path, data = packet
|
code, packet_id, path, data = packet
|
||||||
value_by_name = json.loads(data)
|
value_by_name = json.loads(data)
|
||||||
event = value_by_name['name']
|
event = value_by_name['name']
|
||||||
args = value_by_name.get('args', [])
|
args = value_by_name.get('args', [])
|
||||||
if packet_id:
|
self._send_args(event, args, path, packet_id, find_event_callback)
|
||||||
args.append(self._prepare_to_send_ack(path, packet_id))
|
|
||||||
find_event_callback(event)(*args)
|
|
||||||
|
|
||||||
def _on_ack(self, packet, find_event_callback):
|
def _on_ack(self, packet, find_event_callback):
|
||||||
code, packet_id, path, data = packet
|
code, packet_id, path, data = packet
|
||||||
|
|
@ -451,7 +456,7 @@ class SocketIO(object):
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return
|
return
|
||||||
args = json.loads(data_parts[1]) if len(data_parts) > 1 else []
|
args = json.loads(data_parts[1]) if len(data_parts) > 1 else []
|
||||||
ack_callback(*args)
|
ack_callback(args)
|
||||||
|
|
||||||
def _on_error(self, packet, find_event_callback):
|
def _on_error(self, packet, find_event_callback):
|
||||||
code, packet_id, path, data = packet
|
code, packet_id, path, data = packet
|
||||||
|
|
@ -515,7 +520,7 @@ def _get_socketIO_session(is_secure, base_url, **kw):
|
||||||
response = _get_response(requests.get, server_url, **kw)
|
response = _get_response(requests.get, server_url, **kw)
|
||||||
except TimeoutError as e:
|
except TimeoutError as e:
|
||||||
raise ConnectionError(e)
|
raise ConnectionError(e)
|
||||||
response_parts = response.text.split(':')
|
response_parts = _get_text(response).split(':')
|
||||||
return _SocketIOSession(
|
return _SocketIOSession(
|
||||||
id=response_parts[0],
|
id=response_parts[0],
|
||||||
heartbeat_timeout=int(response_parts[1]),
|
heartbeat_timeout=int(response_parts[1]),
|
||||||
|
|
|
||||||
5
socketIO_client/symmetries.py
Normal file
5
socketIO_client/symmetries.py
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
def _get_text(response):
|
||||||
|
try:
|
||||||
|
return response.text # requests 2.7.0
|
||||||
|
except AttributeError:
|
||||||
|
return response.content # requests 0.8.2
|
||||||
|
|
@ -8,8 +8,10 @@ import socket
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
import websocket
|
import websocket
|
||||||
|
import re
|
||||||
|
|
||||||
from .exceptions import ConnectionError, TimeoutError
|
from .exceptions import ConnectionError, TimeoutError
|
||||||
|
from .symmetries import _get_text
|
||||||
|
|
||||||
|
|
||||||
if not hasattr(websocket, 'create_connection'):
|
if not hasattr(websocket, 'create_connection'):
|
||||||
|
|
@ -84,6 +86,8 @@ class _AbstractTransport(object):
|
||||||
def send_packet(self, code, path='', data='', callback=None):
|
def send_packet(self, code, path='', data='', callback=None):
|
||||||
packet_id = self.set_ack_callback(callback) if callback else ''
|
packet_id = self.set_ack_callback(callback) if callback else ''
|
||||||
packet_parts = str(code), packet_id, path, encode_unicode(data)
|
packet_parts = str(code), packet_id, path, encode_unicode(data)
|
||||||
|
if not data:
|
||||||
|
packet_parts = packet_parts[:-1]
|
||||||
packet_text = ':'.join(packet_parts)
|
packet_text = ':'.join(packet_parts)
|
||||||
self.send(packet_text)
|
self.send(packet_text)
|
||||||
self._log(logging.DEBUG, '[packet sent] %s', packet_text)
|
self._log(logging.DEBUG, '[packet sent] %s', packet_text)
|
||||||
|
|
@ -94,8 +98,17 @@ class _AbstractTransport(object):
|
||||||
yield self._packets.pop(0)
|
yield self._packets.pop(0)
|
||||||
except IndexError:
|
except IndexError:
|
||||||
pass
|
pass
|
||||||
for packet_text in self.recv(timeout=timeout):
|
for packet_texts in self.recv(timeout=timeout):
|
||||||
|
#remove packet separator
|
||||||
|
packet_texts = re.sub('^\xef\xbf\xbd\w+\xef\xbf\xbd', '',
|
||||||
|
packet_texts)
|
||||||
|
|
||||||
|
packets = packet_texts.split('\xef\xbf\xbd')[::2]
|
||||||
|
|
||||||
|
for packet_text in packets:
|
||||||
self._log(logging.DEBUG, '[packet received] %s', packet_text)
|
self._log(logging.DEBUG, '[packet received] %s', packet_text)
|
||||||
|
sep_count = packet_text.count('\xef\xbf\xbd')/2
|
||||||
|
|
||||||
try:
|
try:
|
||||||
packet_parts = packet_text.split(':', 3)
|
packet_parts = packet_text.split(':', 3)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
|
|
@ -109,6 +122,9 @@ class _AbstractTransport(object):
|
||||||
code, packet_id, path = packet_parts
|
code, packet_id, path = packet_parts
|
||||||
elif 1 == packet_count:
|
elif 1 == packet_count:
|
||||||
code = packet_parts[0]
|
code = packet_parts[0]
|
||||||
|
if code and len(code) > 1:
|
||||||
|
code = code[-1]
|
||||||
|
|
||||||
yield code, packet_id, path, data
|
yield code, packet_id, path, data
|
||||||
|
|
||||||
def _enqueue_packet(self, packet):
|
def _enqueue_packet(self, packet):
|
||||||
|
|
@ -142,6 +158,8 @@ class _WebsocketTransport(_AbstractTransport):
|
||||||
http_session = _prepare_http_session(kw)
|
http_session = _prepare_http_session(kw)
|
||||||
req = http_session.prepare_request(requests.Request('GET', url))
|
req = http_session.prepare_request(requests.Request('GET', url))
|
||||||
headers = ['%s: %s' % item for item in req.headers.items()]
|
headers = ['%s: %s' % item for item in req.headers.items()]
|
||||||
|
headers.append('Connection: keep-alive')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self._connection = websocket.create_connection(url, header=headers)
|
self._connection = websocket.create_connection(url, header=headers)
|
||||||
except socket.timeout as e:
|
except socket.timeout as e:
|
||||||
|
|
@ -223,7 +241,7 @@ class _XHR_PollingTransport(_AbstractTransport):
|
||||||
params=self._params,
|
params=self._params,
|
||||||
timeout=timeout or TIMEOUT_IN_SECONDS,
|
timeout=timeout or TIMEOUT_IN_SECONDS,
|
||||||
stream=True)
|
stream=True)
|
||||||
response_text = response.text
|
response_text = _get_text(response)
|
||||||
if not response_text.startswith(BOUNDARY):
|
if not response_text.startswith(BOUNDARY):
|
||||||
yield response_text
|
yield response_text
|
||||||
return
|
return
|
||||||
|
|
@ -279,7 +297,7 @@ class _JSONP_PollingTransport(_AbstractTransport):
|
||||||
params=self._params,
|
params=self._params,
|
||||||
headers={'content-type': 'text/javascript; charset=UTF-8'},
|
headers={'content-type': 'text/javascript; charset=UTF-8'},
|
||||||
timeout=timeout or TIMEOUT_IN_SECONDS)
|
timeout=timeout or TIMEOUT_IN_SECONDS)
|
||||||
response_text = response.text
|
response_text = _get_text(response)
|
||||||
try:
|
try:
|
||||||
self._id, response_text = self.RESPONSE_PATTERN.match(
|
self._id, response_text = self.RESPONSE_PATTERN.match(
|
||||||
response_text).groups()
|
response_text).groups()
|
||||||
|
|
@ -317,10 +335,10 @@ def _get_response(request, *args, **kw):
|
||||||
response = request(*args, **kw)
|
response = request(*args, **kw)
|
||||||
except requests.exceptions.Timeout as e:
|
except requests.exceptions.Timeout as e:
|
||||||
raise TimeoutError(e)
|
raise TimeoutError(e)
|
||||||
except requests.exceptions.ConnectionError as e:
|
|
||||||
raise ConnectionError(e)
|
|
||||||
except requests.exceptions.SSLError as e:
|
except requests.exceptions.SSLError as e:
|
||||||
raise ConnectionError('could not negotiate SSL (%s)' % e)
|
raise ConnectionError('could not negotiate SSL (%s)' % e)
|
||||||
|
except requests.exceptions.ConnectionError as e:
|
||||||
|
raise ConnectionError(e)
|
||||||
status = response.status_code
|
status = response.status_code
|
||||||
if 200 != status:
|
if 200 != status:
|
||||||
raise ConnectionError('unexpected status code (%s)' % status)
|
raise ConnectionError('unexpected status code (%s)' % status)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue