diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..f00bd00 --- /dev/null +++ b/.python-version @@ -0,0 +1,5 @@ +3.9.4 +3.8.9 +3.7.10 +3.6.13 +2.7.18 diff --git a/.travis.yml b/.travis.yml index d345f1d..8c22b32 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,18 +2,20 @@ language: python python: - 2.7 - - 3.3 - - 3.5 + - 3.6 + - 3.7 + - 3.8 + - 3.9 before_install: - sudo apt-get install python-dev libevent-dev - pip install Cython install: - - if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then pip install -r requirements/py2kreqs.txt; fi + - if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then pip install -r requirements/py2kreqs.txt; fi - if [[ $TRAVIS_PYTHON_VERSION == 3* ]]; then pip install -r requirements/py3kreqs.txt; fi - python setup.py install -script: +script: - if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then py.test -v; fi - if [[ $TRAVIS_PYTHON_VERSION == 3* ]]; then py.test -v; fi diff --git a/CHANGELOG.md b/CHANGELOG.md index d26ed44..89d6e57 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,11 @@ ## Unreleased [Full Changelog](https://github.com/Lawouach/WebSocket-for-Python/compare/0.5.1...master) +**Changes:** + + * Upgrade Python support to include 3.6, 3.7, 3.8 and 3.9 + * Drop support for Python 3.* < 3.6 (Python 2.7 remains) + ## [0.5.1](https://github.com/Lawouach/WebSocket-for-Python/tree/0.5.1) (2018-02-28) [Full Changelog](https://github.com/Lawouach/WebSocket-for-Python/compare/0.5.0...0.5.1) **Merged pull requests:** diff --git a/setup.py b/setup.py index c9452fa..72fc274 100644 --- a/setup.py +++ b/setup.py @@ -58,9 +58,10 @@ def find_package_modules(self, package, package_dir): 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.3', - 'Programming Language :: Python :: 3.4', - 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', 'Topic :: Communications', diff --git a/test/autobahn_test_servers.py b/test/autobahn_test_servers.py index 3e33f82..fdf8d5d 100644 --- a/test/autobahn_test_servers.py +++ b/test/autobahn_test_servers.py @@ -85,7 +85,7 @@ def run_python3_asyncio(host="127.0.0.1", port=9009): wsaccel.patch_ws4py() from ws4py.async_websocket import EchoWebSocket from ws4py.server.tulipserver import WebSocketProtocol - + loop = asyncio.get_event_loop() def start_server(): @@ -121,14 +121,14 @@ def run_autobahn_server(host="127.0.0.1", port=9003): from twisted.internet import reactor from autobahn.twisted.websocket import WebSocketServerProtocol, \ WebSocketServerFactory - + class MyServerProtocol(WebSocketServerProtocol): def onMessage(self, payload, isBinary): self.sendMessage(payload, isBinary) logger = logging.getLogger('autobahn_testsuite') logger.warning("Serving Autobahn server on %s:%s" % (host, port)) - + factory = WebSocketServerFactory("ws://%s:%d" % (host, port)) factory.protocol = MyServerProtocol @@ -142,7 +142,7 @@ def run_python_wsgi(host="127.0.0.1", port=9002): """ run_python_wsgi_async(host, port, False) -def run_python_wsgi_async(host="127.0.0.1", port=9010, async=True): +def run_python_wsgi_async(host="127.0.0.1", port=9010, async_=True): """ Runs wsgi server on python 2.x with async middleware" """ @@ -153,7 +153,7 @@ def run_python_wsgi_async(host="127.0.0.1", port=9010, async=True): from ws4py.server.wsgiutils import WebSocketWSGIApplication app = WebSocketWSGIApplication(handler_cls=EchoWebSocket) - if async: + if async_: def middleware(app): def later(environ, start_response): for part in app(environ, start_response): diff --git a/test/test_logger.py b/test/test_logger.py index a39e56d..5e84624 100644 --- a/test/test_logger.py +++ b/test/test_logger.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -import logging +import logging import logging.handlers as handlers import os, os.path import unittest @@ -15,27 +15,31 @@ def clean_logger(): except KeyError: pass logger.removeHandler(handler) - + class WSTestLogger(unittest.TestCase): + LOG_FILE = './my.log' + def tearDown(self): clean_logger() - + if os.path.exists(self.LOG_FILE): + os.remove(self.LOG_FILE) + def test_named_logger(self): - logger = configure_logger(stdout=False, filepath='./my.log') + logger = configure_logger(stdout=False, filepath=self.LOG_FILE) logger = logging.getLogger('ws4py') self.assertEqual(logger.getEffectiveLevel(), logging.INFO) - + def test_level(self): - logger = configure_logger(stdout=True, filepath='./my.log', + logger = configure_logger(stdout=True, filepath=self.LOG_FILE, level=logging.DEBUG) self.assertEqual(logger.getEffectiveLevel(), logging.DEBUG) for handler in logger.handlers: self.assertEqual(handler.level, logging.DEBUG) - + def test_file_logger(self): - filepath = os.path.abspath('./my.log') + filepath = os.path.abspath(self.LOG_FILE) logger = configure_logger(stdout=False, filepath=filepath) for handler in logger.handlers: if isinstance(handler, handlers.RotatingFileHandler): diff --git a/tox.ini b/tox.ini index 6621a19..66ac4ba 100644 --- a/tox.ini +++ b/tox.ini @@ -4,7 +4,10 @@ # and then run "tox" from this directory. [tox] -envlist = py27 +envlist = py27,py36,py37,py38,py39 [testenv] commands = python setup.py test +deps = + py27: -r requirements/py2kreqs.txt + {py36,py37,py38,py39}: -r requirements/py3kreqs.txt \ No newline at end of file diff --git a/ws4py/_asyncio_compat.py b/ws4py/_asyncio_compat.py new file mode 100644 index 0000000..baec0cd --- /dev/null +++ b/ws4py/_asyncio_compat.py @@ -0,0 +1,9 @@ +"""Provide compatibility over different versions of asyncio.""" + +import asyncio + +if hasattr(asyncio, "async"): + # Compatibility for Python 3.3 and older + ensure_future = getattr(asyncio, "async") +else: + ensure_future = asyncio.ensure_future \ No newline at end of file diff --git a/ws4py/async_websocket.py b/ws4py/async_websocket.py index 9e2a4c7..177f471 100644 --- a/ws4py/async_websocket.py +++ b/ws4py/async_websocket.py @@ -19,6 +19,7 @@ import types from ws4py.websocket import WebSocket as _WebSocket +from ws4py import _asyncio_compat from ws4py.messaging import Message __all__ = ['WebSocket', 'EchoWebSocket'] @@ -84,7 +85,7 @@ def close_connection(self): def closeit(): yield from self.proto.writer.drain() self.proto.writer.close() - asyncio.async(closeit()) + _asyncio_compat.ensure_future(closeit()) def _write(self, data): """ @@ -94,7 +95,7 @@ def _write(self, data): def sendit(data): self.proto.writer.write(data) yield from self.proto.writer.drain() - asyncio.async(sendit(data)) + _asyncio_compat.ensure_future(sendit(data)) @asyncio.coroutine def run(self): diff --git a/ws4py/server/tulipserver.py b/ws4py/server/tulipserver.py index 2786c16..fdf749c 100644 --- a/ws4py/server/tulipserver.py +++ b/ws4py/server/tulipserver.py @@ -9,6 +9,7 @@ from ws4py import WS_KEY, WS_VERSION from ws4py.exc import HandshakeError from ws4py.websocket import WebSocket +from ws4py import _asyncio_compat LF = b'\n' CRLF = b'\r\n' @@ -25,7 +26,7 @@ def __init__(self, handler_cls): def _pseudo_connected(self, reader, writer): pass - + def connection_made(self, transport): """ A peer is now connected and we receive an instance @@ -40,17 +41,17 @@ def connection_made(self, transport): #self.stream.set_transport(transport) asyncio.StreamReaderProtocol.connection_made(self, transport) # Let make it concurrent for others to tag along - f = asyncio.async(self.handle_initial_handshake()) + f = _asyncio_compat.ensure_future(self.handle_initial_handshake()) f.add_done_callback(self.terminated) @property def writer(self): return self._stream_writer - + @property def reader(self): return self._stream_reader - + def terminated(self, f): if f.done() and not f.cancelled(): ex = f.exception() @@ -70,12 +71,12 @@ def close(self): transport. """ self.ws.close() - + def timeout(self): self.ws.close_connection() if self.ws.started: self.ws.closed(1002, "Peer connection timed-out") - + def connection_lost(self, exc): """ The peer connection is now, the closing @@ -88,7 +89,7 @@ def connection_lost(self, exc): self.ws.close_connection() if self.ws.started: self.ws.closed(1002, "Peer connection was lost") - + @asyncio.coroutine def handle_initial_handshake(self): """ @@ -100,15 +101,15 @@ def handle_initial_handshake(self): """ request_line = yield from self.next_line() method, uri, req_protocol = request_line.strip().split(SPACE, 2) - + # GET required if method.upper() != b'GET': raise HandshakeError('HTTP method must be a GET') - + headers = yield from self.read_headers() if req_protocol == b'HTTP/1.1' and 'Host' not in headers: raise ValueError("Missing host header") - + for key, expected_value in [('Upgrade', 'websocket'), ('Connection', 'upgrade')]: actual_value = headers.get(key, '').lower() @@ -160,7 +161,7 @@ def handle_initial_handshake(self): self.ws.protocols = ws_protocols self.ws.extensions = ws_extensions self.ws.headers = headers - + response = [req_protocol + b' 101 Switching Protocols'] response.append(b'Upgrade: websocket') response.append(b'Content-Type: text/plain') @@ -184,7 +185,7 @@ def handle_websocket(self): exchange is completed and terminated. """ yield from self.ws.run() - + @asyncio.coroutine def read_headers(self): """ @@ -198,21 +199,21 @@ def read_headers(self): if line == CRLF: break return BytesHeaderParser().parsebytes(headers) - + @asyncio.coroutine def next_line(self): """ Reads data until \r\n is met and then return all read - bytes. + bytes. """ line = yield from self.reader.readline() if not line.endswith(CRLF): raise ValueError("Missing mandatory trailing CRLF") return line - + if __name__ == '__main__': from ws4py.async_websocket import EchoWebSocket - + loop = asyncio.get_event_loop() def start_server():