diff --git a/.travis.yml b/.travis.yml index bf850b3c58..114f0be9af 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,11 +1,35 @@ language: python + +# because python3.7 is not available on trusty +# @see https://github.com/travis-ci/travis-ci/issues/9815 +dist: xenial + +# Test on python3 python: -- '2.7' + - 3.5 # debian + - 3.6 # ubuntu / amazonlinux + - 3.7 # fedora + - nightly + +# Allow failures on cpython nightly build +matrix: + fast_finish: true + allow_failures: + - python: nightly + install: - pip install -r cyclone/tests/test_requirements.txt - pip install coveralls + +# cyclone has to be installed +# so as trial (twisted test framework) could run +before_script: python setup.py install + script: coverage run `which trial` cyclone after_success: coveralls + +# deploy on pypi +# only on 3.6 (avoid multiple deployment) deploy: provider: pypi user: fiorix @@ -14,4 +38,5 @@ deploy: on: tags: true branch: master + condition: $TRAVIS_PYTHON_VERSION = 3.6 distributions: "sdist bdist_wheel" diff --git a/README.md b/README.md index 2240b200b1..a252e41fcf 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,14 @@ Cyclone ======= [![Build Status](https://travis-ci.org/fiorix/cyclone.svg?branch=master)](https://travis-ci.org/fiorix/cyclone) +[![Coverage Status](https://coveralls.io/repos/github/fiorix/cyclone/badge.svg?branch=master)](https://coveralls.io/github/fiorix/cyclone?branch=master) +[![Supported Python versions](https://pypi.org/project/cyclone)](https://img.shields.io/pypi/pyversions/cyclone.svg) Cyclone is a web server framework for Python, that implements the Tornado API as a Twisted protocol. +:warning: `cyclone` does not support `python` **2.x** anymore :warning: + See http://cyclone.io for details. Installation diff --git a/appskel/default/modname/config.py b/appskel/default/modname/config.py index 0ec1d93487..b130352ba1 100644 --- a/appskel/default/modname/config.py +++ b/appskel/default/modname/config.py @@ -79,6 +79,6 @@ def my_parse_config(filename): def parse_config(filename): try: return my_parse_config(filename) - except Exception as e: + except Exception, e: print("Error parsing %s: %s" % (filename, e)) sys.exit(1) diff --git a/appskel/default/modname/storage.py b/appskel/default/modname/storage.py index 23d7df9f1d..26abbeaf3c 100644 --- a/appskel/default/modname/storage.py +++ b/appskel/default/modname/storage.py @@ -68,7 +68,7 @@ def setup(cls, conf): def _ping_mysql(): try: yield cls.mysql.runQuery("select 1") - except Exception as e: + except Exception, e: log.msg("MySQL ping error:", e) else: if conf["mysql_settings"].debug: diff --git a/appskel/default/modname/views.py b/appskel/default/modname/views.py index 332c832b4e..9dae22cc1d 100644 --- a/appskel/default/modname/views.py +++ b/appskel/default/modname/views.py @@ -50,7 +50,7 @@ def get(self): if self.redis: try: response = yield self.redis.get("foo") - except Exception as e: + except Exception, e: log.msg("Redis query failed: %s" % str(e)) raise cyclone.web.HTTPError(503) # Service Unavailable else: @@ -65,7 +65,7 @@ def get(self): if self.mysql: try: response = yield self.mysql.runQuery("select now()") - except Exception as e: + except Exception, e: log.msg("MySQL query failed: %s" % str(e)) raise cyclone.web.HTTPError(503) # Service Unavailable else: diff --git a/appskel/signup/modname/config.py b/appskel/signup/modname/config.py index 285acd50f1..dbcc3a2d31 100644 --- a/appskel/signup/modname/config.py +++ b/appskel/signup/modname/config.py @@ -88,6 +88,6 @@ def my_parse_config(filename): def parse_config(filename): try: return my_parse_config(filename) - except Exception as e: + except Exception, e: print("Error parsing %s: %s" % (filename, e)) sys.exit(1) diff --git a/appskel/signup/modname/storage.py b/appskel/signup/modname/storage.py index 4209a584c7..1d41f15d4c 100644 --- a/appskel/signup/modname/storage.py +++ b/appskel/signup/modname/storage.py @@ -104,7 +104,7 @@ def setup(cls, conf): def _ping_mysql(): try: yield cls.mysql.runQuery("select 1") - except Exception as e: + except Exception, e: log.msg("MySQL ping error:", e) else: if conf["mysql_settings"].debug: diff --git a/appskel/signup/modname/views.py b/appskel/signup/modname/views.py index f6cb07cacd..4e85fa010a 100644 --- a/appskel/signup/modname/views.py +++ b/appskel/signup/modname/views.py @@ -181,7 +181,7 @@ def post(self): try: r = yield cyclone.mail.sendmail(self.settings.email_settings, msg) - except Exception as e: + except Exception, e: # delete password from redis yield self.redis.delete(k) @@ -337,7 +337,7 @@ def post(self): try: r = yield cyclone.mail.sendmail(self.settings.email_settings, msg) - except Exception as e: + except Exception, e: # do not delete from redis # yield self.redis.delete(k) diff --git a/cyclone/auth.py b/cyclone/auth.py index d9aeb2bbb7..c87499d62f 100644 --- a/cyclone/auth.py +++ b/cyclone/auth.py @@ -57,23 +57,10 @@ def _on_auth(self, user): import hashlib import hmac import time +import urllib +from urllib import parse as urllib_parse import uuid -try: - import urlparse # py2 -except ImportError: - import urllib.parse as urlparse # py3 - -try: - import urllib.parse as urllib_parse # py3 -except ImportError: - import urllib as urllib_parse # py2 - -try: - long # py2 -except NameError: - long = int # py3 - class OpenIdMixin(object): """Abstract implementation of OpenID and Attribute Exchange. @@ -114,7 +101,7 @@ def get_authenticated_user(self, callback): postdata=urllib_parse.urlencode(args)).addBoth(callback) def _openid_args(self, callback_uri, ax_attrs=[], oauth_scope=None): - url = urlparse.urljoin(self.request.full_url(), callback_uri) + url = urllib_parse.urljoin(self.request.full_url(), callback_uri) args = { "openid.ns": "http://specs.openid.net/auth/2.0", "openid.claimed_id": @@ -122,7 +109,7 @@ def _openid_args(self, callback_uri, ax_attrs=[], oauth_scope=None): "openid.identity": "http://specs.openid.net/auth/2.0/identifier_select", "openid.return_to": url, - "openid.realm": urlparse.urljoin(url, '/'), + "openid.realm": urllib_parse.urljoin(url, '/'), "openid.mode": "checkid_setup", } if ax_attrs: @@ -291,7 +278,7 @@ def _oauth_request_token_url(self, callback_uri=None, extra_params=None): if callback_uri == "oob": args["oauth_callback"] = "oob" elif callback_uri: - args["oauth_callback"] = urlparse.urljoin( + args["oauth_callback"] = urllib_parse.urljoin( self.request.full_url(), callback_uri) if extra_params: args.update(extra_params) @@ -314,7 +301,7 @@ def _on_request_token(self, authorize_url, callback_uri, response): self.finish(authorize_url + "?" + urllib_parse.urlencode(args)) return elif callback_uri: - args["oauth_callback"] = urlparse.urljoin( + args["oauth_callback"] = urllib_parse.urljoin( self.request.full_url(), callback_uri) self.redirect(authorize_url + "?" + urllib_parse.urlencode(args)) @@ -820,11 +807,11 @@ def authenticate_redirect(self, callback_uri=None, cancel_uri=None, "v": "1.0", "fbconnect": "true", "display": "page", - "next": urlparse.urljoin(self.request.full_url(), callback_uri), + "next": urllib_parse.urljoin(self.request.full_url(), callback_uri), "return_session": "true", } if cancel_uri: - args["cancel_url"] = urlparse.urljoin( + args["cancel_url"] = urllib_parse.urljoin( self.request.full_url(), cancel_uri) if extended_permissions: if isinstance(extended_permissions, (unicode_type, bytes_type)): @@ -908,7 +895,7 @@ def _on_stream(self, stream): args["api_key"] = self.settings["facebook_api_key"] args["v"] = "1.0" args["method"] = method - args["call_id"] = str(long(time.time() * 1e6)) + args["call_id"] = str(int(time.time() * 1e6)) args["format"] = "json" args["sig"] = self._signature(args) url = "http://api.facebook.com/restserver.php?" + \ @@ -940,7 +927,7 @@ def _parse_response(self, callback, response): return try: json = escape.json_decode(response.body) - except: + except Exception: log.msg("Invalid JSON from Facebook: %r" % response.body) callback(None) return @@ -954,7 +941,7 @@ def _parse_response(self, callback, response): def _signature(self, args): parts = ["%s=%s" % (n, args[n]) for n in sorted(args.keys())] body = "".join(parts) + self.settings["facebook_secret"] - if isinstance(body, unicode): + if isinstance(body, str): body = body.encode("utf-8") return hashlib.md5(body).hexdigest() @@ -966,7 +953,7 @@ class FacebookGraphMixin(OAuth2Mixin): _OAUTH_NO_CALLBACKS = False def get_authenticated_user(self, redirect_uri, client_id, client_secret, - code, callback, extra_fields=None): + code, callback, extra_fields=None): """Handles the login for the Facebook user, returning a user object. Example usage:: @@ -1014,7 +1001,7 @@ def _on_login(self, user): def _on_access_token(self, redirect_uri, client_id, client_secret, callback, fields, response): if response.error: - log.warning('Facebook auth error: %s' % str(response)) + log.msg('Facebook auth error: %s' % str(response)) callback(None) return @@ -1045,7 +1032,7 @@ def _on_get_user_info(self, callback, session, fields, user): callback(fieldmap) def facebook_request(self, path, callback, access_token=None, - post_args=None, **args): + post_args=None, **args): """Fetches the given relative API path, e.g., "/btaylor/picture" If the request is a POST, post_args should be provided. Query @@ -1090,14 +1077,13 @@ def _on_post(self, new_entry): callback = self.async_callback(self._on_facebook_request, callback) if post_args is not None: httpclient.fetch(url, method="POST", - postdata=urllib_parse.urlencode(post_args)).addCallback(callback) + postdata=urllib_parse.urlencode(post_args)).addCallback(callback) else: httpclient.fetch(url).addCallback(callback) def _on_facebook_request(self, callback, response): if response.error: - log.warning("Error response %s fetching %s", response.error, - response.request.url) + log.msg("Error response %s fetching %s", response.error, response.request.url) callback(None) return callback(escape.json_decode(response.body)) @@ -1108,7 +1094,7 @@ def _oauth_signature(consumer_token, method, url, parameters={}, token=None): See http://oauth.net/core/1.0/#signing_process """ - parts = urlparse.urlparse(url) + parts = urllib_parse.urlparse(url) scheme, netloc, path = parts[:3] normalized_url = scheme.lower() + "://" + netloc.lower() + path @@ -1133,7 +1119,7 @@ def _oauth10a_signature(consumer_token, See http://oauth.net/core/1.0a/#signing_process """ - parts = urlparse.urlparse(url) + parts = urllib_parse.urlparse(url) scheme, netloc, path = parts[:3] normalized_url = scheme.lower() + "://" + netloc.lower() + path @@ -1144,10 +1130,8 @@ def _oauth10a_signature(consumer_token, for k, v in sorted(parameters.items()))) base_string = "&".join(_oauth_escape(e) for e in base_elems) - key_elems = [escape.utf8( - urllib_parse.quote(consumer_token["secret"], safe='~'))] - key_elems.append(escape.utf8( - urllib_parse.quote(token["secret"], safe='~') if token else "")) + key_elems = [escape.utf8(urllib_parse.quote(consumer_token["secret"], safe='~'))] + key_elems.append(escape.utf8(urllib_parse.quote(token["secret"], safe='~') if token else "")) key = "&".join(key_elems) hash = hmac.new(key, escape.utf8(base_string), hashlib.sha1) @@ -1161,7 +1145,7 @@ def _oauth_escape(val): def _oauth_parse_response(body): - p = escape.parse_qs(body, keep_blank_values=False) + p = urllib_parse.parse_qs(body, keep_blank_values=False) token = dict(key=p["oauth_token"][0], secret=p["oauth_token_secret"][0]) # Add the extra parameters the Provider included to the token diff --git a/cyclone/escape.py b/cyclone/escape.py index 61d491e40f..f5f8b1e93d 100644 --- a/cyclone/escape.py +++ b/cyclone/escape.py @@ -22,59 +22,18 @@ """ from __future__ import absolute_import, division, with_statement - -try: - from urllib.parse import parse_qs as _parse_qs # py3 -except ImportError: - from urlparse import parse_qs as _parse_qs # Python 2.6+ - -try: - import htmlentitydefs # py2 -except ImportError: - import html.entities as htmlentitydefs # py3 - -try: - import urllib.parse as urllib_parse # py3 -except ImportError: - import urllib as urllib_parse # py2 - +from html import entities as html_entities import re -import sys - +import json +from urllib import parse as urllib_parse from cyclone.util import basestring_type from cyclone.util import bytes_type from cyclone.util import unicode_type -from cyclone.util import unicode_char_type - -try: - from urlparse import parse_qs # Python 2.6+ -except ImportError: - from cgi import parse_qs - -# json module is in the standard library as of python 2.6; fall back to -# simplejson if present for older versions. -try: - import json - assert hasattr(json, "loads") and hasattr(json, "dumps") - _json_decode = json.loads - _json_encode = json.dumps -except Exception: # pragma: nocover - try: - import simplejson - _json_decode = lambda s: simplejson.loads(_unicode(s)) - _json_encode = lambda v: simplejson.dumps(v) - except ImportError: - try: - # For Google AppEngine - from django.utils import simplejson - _json_decode = lambda s: simplejson.loads(_unicode(s)) - _json_encode = lambda v: simplejson.dumps(v) - except ImportError: - def _json_decode(s): - raise NotImplementedError( - "A JSON parser is required, e.g., simplejson at " - "http://pypi.python.org/pypi/simplejson/") - _json_encode = _json_decode + + +_json_decode = json.loads +_json_encode = json.dumps + _XHTML_ESCAPE_RE = re.compile('[&<>"\']') @@ -83,8 +42,7 @@ def _json_decode(s): def xhtml_escape(value): """Escapes a string so it is valid within XML or XHTML.""" - return _XHTML_ESCAPE_RE.sub(lambda match: - _XHTML_ESCAPE_DICT[match.group(0)], to_basestring(value)) + return _XHTML_ESCAPE_RE.sub(lambda match: _XHTML_ESCAPE_DICT[match.group(0)], to_basestring(value)) def xhtml_unescape(value): @@ -116,55 +74,22 @@ def squeeze(value): def url_escape(value): """Returns a valid URL-encoded version of the given value.""" - return urllib_parse.quote_plus(utf8(value)) + return urllib_parse.quote_plus(value) -if sys.version_info[0] < 3: - def url_unescape(value, encoding='utf-8'): - """Decodes the given value from a URL. +def url_unescape(value, encoding='utf-8'): + """Decodes the given value from a URL. - The argument may be either a byte or unicode string. + The argument may be either a byte or unicode string. - If encoding is None, the result will be a byte string. Otherwise, - the result is a unicode string in the specified encoding. - """ - if encoding is None: - return urllib_parse.unquote_plus(utf8(value)) - else: - return unicode_type(urllib_parse.unquote_plus(utf8(value)), encoding) + If encoding is None, the result will be a byte string. Otherwise, + the result is a unicode string in the specified encoding. + """ + return urllib_parse.unquote_plus(value) - parse_qs_bytes = parse_qs -else: - def url_unescape(value, encoding='utf-8'): - """Decodes the given value from a URL. - The argument may be either a byte or unicode string. +parse_qs_bytes = urllib_parse.parse_qs - If encoding is None, the result will be a byte string. Otherwise, - the result is a unicode string in the specified encoding. - """ - if encoding is None: - value = to_basestring(value).replace('+', ' ') - return urllib_parse.unquote_to_bytes(value) - else: - return urllib_parse.unquote_plus(to_basestring(value), encoding=encoding) - - def parse_qs_bytes(qs, keep_blank_values=False, strict_parsing=False): - """Parses a query string like urlparse.parse_qs, but returns the - values as byte strings. - - Keys still become type str (interpreted as latin1 in python3!) - because it's too painful to keep them as byte strings in - python3 and in practice they're nearly always ascii anyway. - """ - # This is gross, but python3 doesn't give us another way. - # Latin1 is the universal donor of character encodings. - result = _parse_qs(qs, keep_blank_values, strict_parsing, - encoding='latin1', errors='strict') - encoded = {} - for k, v in result.items(): - encoded[k] = [i.encode('latin1') for i in v] - return encoded _UTF8_TYPES = (bytes, type(None)) @@ -180,6 +105,7 @@ def utf8(value): assert isinstance(value, unicode_type) return value.encode("utf-8") + _TO_UNICODE_TYPES = (unicode_type, type(None)) @@ -191,7 +117,7 @@ def to_unicode(value): """ if isinstance(value, _TO_UNICODE_TYPES): return value - assert isinstance(value, bytes_type) + #assert isinstance(value, bytes_type) return value.decode("utf-8") # to_unicode was previously named _unicode not because it was private, @@ -230,7 +156,7 @@ def recursive_unicode(obj): """ if isinstance(obj, dict): return dict((recursive_unicode(k), recursive_unicode(v)) - for (k, v) in obj.iteritems()) + for (k, v) in obj.items()) elif isinstance(obj, list): return list(recursive_unicode(i) for i in obj) elif isinstance(obj, tuple): @@ -247,10 +173,12 @@ def recursive_unicode(obj): # This regex should avoid those problems. # Use to_unicode instead of tornado.util.u - we don't want backslashes getting # processed as escapes. -_URL_RE = re.compile(to_unicode(r"""\b((?:([\w-]+):(/{1,3})|www[.])""" - r"""(?:(?:(?:[^\s&()]|&|")*""" - r"""(?:[^!"#$%&'()*+,.:;<=>?@\[\]^`{|}~\s]))""" - r"""|(?:\((?:[^\s&()]|&|")*\)))+)""")) + + +_URL_RE = re.compile(r"""\b((?:([\w-]+):(/{1,3})|www[.])""" + r"""(?:(?:(?:[^\s&()]|&|")*""" + r"""(?:[^!"#$%&'()*+,.:;<=>?@\[\]^`{|}~\s]))""" + r"""|(?:\((?:[^\s&()]|&|")*\)))+)""") def linkify(text, shorten=False, extra_params="", @@ -339,8 +267,7 @@ def make_link(m): # have a status bar, such as Safari by default) params += ' title="%s"' % href - return ('%s'.decode("unicode_escape") % - (href, params, url)) + return '%s' % (href, params, url) # First HTML-escape so that our strings are all safe. # The regex is modified to avoid character entites other than & so @@ -352,7 +279,7 @@ def make_link(m): def _convert_entity(m): if m.group(1) == "#": try: - return unicode_char_type(int(m.group(2))) + return chr(int(m.group(2))) except ValueError: return "&#%s;" % m.group(2) try: @@ -363,8 +290,9 @@ def _convert_entity(m): def _build_unicode_map(): unicode_map = {} - for name, value in htmlentitydefs.name2codepoint.items(): - unicode_map[name] = unicode_char_type(value) + for name, value in html_entities.name2codepoint.items(): + unicode_map[name] = chr(value) return unicode_map + _HTML_UNICODE_MAP = _build_unicode_map() diff --git a/cyclone/httpclient.py b/cyclone/httpclient.py index 616556dd44..865dd2fc6e 100644 --- a/cyclone/httpclient.py +++ b/cyclone/httpclient.py @@ -18,19 +18,6 @@ """Non-blocking HTTP client""" import functools - -try: - from types import ListType -except ImportError: - # python 3 compatibility - ListType = list - -try: - from types import DictType -except ImportError: - # python 3 compatibility - DictType = dict - from cyclone import escape from cyclone.web import HTTPError @@ -49,6 +36,7 @@ agent = Agent(reactor) proxy_agent = ProxyAgent(None, reactor) + @implementer(IBodyProducer) class StringProducer(object): def __init__(self, body): @@ -127,7 +115,7 @@ def fetch(self): headers = dict(response.headers.getAllRawHeaders()) location = headers.get("Location") if location: - if isinstance(location, ListType): + if isinstance(location, list): location = location[0] #print("redirecting to:", location) @@ -140,7 +128,6 @@ def fetch(self): break else: break - response.error = response.code >= 400 response.headers = dict(response.headers.getAllRawHeaders()) # HTTP 204 and 304 responses have no body @@ -203,7 +190,7 @@ def fetch(url, *args, **kwargs): port string as second member; describing which proxy to use when making request """ - return HTTPClient(escape.utf8(url), *args, **kwargs).fetch() + return HTTPClient(url, *args, **kwargs).fetch() class JsonRPC: @@ -250,7 +237,7 @@ def _success(response, deferred): data = escape.json_decode(response.body) error = data.get("error") if error: - if isinstance(error, DictType) and 'message' in error: + if isinstance(error, dict) and 'message' in error: # JSON-RPC spec is not very verbose about error schema, # but it should look like {'code': 0, 'message': 'msg'} deferred.errback(Exception(error['message'])) diff --git a/cyclone/httpserver.py b/cyclone/httpserver.py index 763b97e883..23a3482a84 100644 --- a/cyclone/httpserver.py +++ b/cyclone/httpserver.py @@ -31,12 +31,7 @@ from __future__ import absolute_import, division, with_statement -try: - import http.cookies as Cookie -except ImportError: - # python 2 compatibility - import Cookie - +from http import cookies as http_cookies import socket import time @@ -48,7 +43,7 @@ from twisted.internet import defer from twisted.internet import interfaces -from cyclone.escape import utf8, native_str, parse_qs_bytes +from cyclone.escape import utf8, native_str, parse_qs_bytes, to_unicode from cyclone import httputil from cyclone.util import bytes_type @@ -62,14 +57,13 @@ class HTTPConnection(basic.LineReceiver): """Handles a connection to an HTTP client, executing HTTP requests. We parse HTTP headers and bodies, and execute the request callback - until the HTTP conection is closed. + until the HTTP connection is closed. If ``xheaders`` is ``True``, we support the ``X-Real-Ip`` and ``X-Scheme`` headers, which override the remote IP and HTTP scheme for all requests. These headers are useful when running Tornado behind a reverse proxy or load balancer. """ - delimiter = "\r\n" def connectionMade(self): self._headersbuffer = [] @@ -96,7 +90,7 @@ def lineReceived(self, line): if line: self._headersbuffer.append(line + self.delimiter) else: - buff = "".join(self._headersbuffer) + buff = b"".join(self._headersbuffer) self._headersbuffer = [] self._on_headers(buff) @@ -105,7 +99,7 @@ def rawDataReceived(self, data): data, rest = data[:self.content_length], data[self.content_length:] self.content_length -= len(data) else: - rest = '' + rest = b'' self._contentbuffer.write(data) if self.content_length == 0: @@ -150,29 +144,27 @@ def _finish_request(self): def _on_headers(self, data): try: - data = native_str(data.decode("latin1")) - eol = data.find("\r\n") + eol = data.find(b"\r\n") start_line = data[:eol] try: - method, uri, version = start_line.split(" ") + method, uri, version = start_line.split(b" ") except ValueError: raise _BadRequestException("Malformed HTTP request line") - if not version.startswith("HTTP/"): - raise _BadRequestException( - "Malformed HTTP version in HTTP Request-Line") + if not version.startswith(b"HTTP/"): + raise _BadRequestException("Malformed HTTP version in HTTP Request-Line") try: - headers = httputil.HTTPHeaders.parse(data[eol:]) + headers = httputil.HTTPHeaders.parse(to_unicode(data[eol:])) content_length = int(headers.get("Content-Length", 0)) except ValueError: - raise _BadRequestException( - "Malformed HTTP headers") + raise _BadRequestException("Malformed HTTP headers") self._request = HTTPRequest( - connection=self, method=method, uri=uri, version=version, - headers=headers, remote_ip=self._remote_ip) + connection=self, method=to_unicode(method), uri=to_unicode(uri), + version=to_unicode(version), + headers=headers, remote_ip=to_unicode(self._remote_ip)) if content_length: if headers.get("Expect") == "100-continue": - self.transport.write("HTTP/1.1 100 (Continue)\r\n\r\n") + self.transport.write(b"HTTP/1.1 100 (Continue)\r\n\r\n") if content_length < 100000: self._contentbuffer = StringIO() @@ -182,7 +174,6 @@ def _on_headers(self, data): self.content_length = content_length self.setRawMode() return - self.request_callback(self._request) except _BadRequestException as e: log.msg("Malformed HTTP request from %s: %s", self._remote_ip, e) @@ -194,7 +185,7 @@ def _on_request_body(self, data): if self._request.method in ("POST", "PATCH", "PUT"): if content_type.startswith("application/x-www-form-urlencoded"): arguments = parse_qs_bytes(native_str(self._request.body)) - for name, values in arguments.iteritems(): + for name, values in arguments.items(): values = [v for v in values if v] if values: self._request.arguments.setdefault(name, @@ -303,7 +294,7 @@ def __init__(self, method, uri, version="HTTP/1.0", headers=None, self.uri = uri self.version = version self.headers = headers or httputil.HTTPHeaders() - self.body = body or "" + self.body = body or b"" if connection and connection.xheaders: # Squid uses X-Forwarded-For, others use X-Real-Ip self.remote_ip = self.headers.get( @@ -340,11 +331,10 @@ def supports_http_1_1(self): def cookies(self): """A dictionary of Cookie.Morsel objects.""" if not hasattr(self, "_cookies"): - self._cookies = Cookie.SimpleCookie() + self._cookies = http_cookies.SimpleCookie() if "Cookie" in self.headers: try: - self._cookies.load( - native_str(self.headers["Cookie"])) + self._cookies.load(native_str(self.headers["Cookie"])) except Exception: self._cookies = {} return self._cookies @@ -393,4 +383,3 @@ def _valid_ip(self, ip): if e.args[0] == socket.EAI_NONAME: return False raise - return True diff --git a/cyclone/httputil.py b/cyclone/httputil.py index 3b0cac7680..7714d8ae2a 100644 --- a/cyclone/httputil.py +++ b/cyclone/httputil.py @@ -27,12 +27,7 @@ from cyclone.escape import utf8 from twisted.python import log - -try: - from urllib.parse import urlencode -except ImportError: - # python 2 compatibility - from urllib import urlencode +from urllib import parse as urllib_parse class HTTPHeaders(dict): @@ -113,14 +108,15 @@ def parse_line(self, line): >>> h.get('content-type') 'text/html' """ - if line[0].isspace(): + temp = native_str(line) + if temp[0].isspace(): # continuation of a multi-line header - new_part = ' ' + line.lstrip() + new_part = ' ' + temp.lstrip() self._as_list[self._last_key][-1] += new_part dict.__setitem__(self, self._last_key, self[self._last_key] + new_part) else: - name, value = line.split(":", 1) + name, value = temp.split(":", 1) self.add(name, value.strip()) @classmethod @@ -203,7 +199,7 @@ def url_concat(url, args): return url if url[-1] not in ('?', '&'): url += '&' if ('?' in url) else '?' - return url + urlencode(args) + return url + urllib_parse.urlencode(args) class HTTPFile(ObjectDict): @@ -255,24 +251,24 @@ def parse_multipart_form_data(boundary, data, arguments, files): # xmpp). I think we're also supposed to handle backslash-escapes # here but I'll save that until we see a client that uses them # in the wild. - if boundary.startswith('"') and boundary.endswith('"'): + if boundary.startswith(b'"') and boundary.endswith(b'"'): boundary = boundary[1:-1] - final_boundary_index = data.rfind("--" + boundary + "--") + final_boundary_index = data.rfind(b"--" + boundary + b"--") if final_boundary_index == -1: log.msg("Invalid multipart/form-data: no final boundary") return - parts = data[:final_boundary_index].split("--" + boundary + "\r\n") + parts = data[:final_boundary_index].split(b"--" + boundary + b"\r\n") for part in parts: if not part: continue - eoh = part.find("\r\n\r\n") + eoh = part.find(b"\r\n\r\n") if eoh == -1: log.msg("multipart/form-data missing headers") continue - headers = HTTPHeaders.parse(part[:eoh].decode("utf-8")) + headers = HTTPHeaders.parse(part[:eoh]) disp_header = headers.get("Content-Disposition", "") disposition, disp_params = _parse_header(disp_header) - if disposition != "form-data" or not part.endswith("\r\n"): + if disposition != "form-data" or not part.endswith(b"\r\n"): log.msg("Invalid multipart/form-data") continue value = part[eoh + 4:-2] diff --git a/cyclone/jsonrpc.py b/cyclone/jsonrpc.py index 32412fc275..673468b09c 100644 --- a/cyclone/jsonrpc.py +++ b/cyclone/jsonrpc.py @@ -24,8 +24,6 @@ `_. """ -import types - import cyclone.escape from cyclone.web import HTTPError, RequestHandler @@ -57,11 +55,9 @@ def post(self, *args): req = cyclone.escape.json_decode(self.request.body) jsonid = req["id"] method = req["method"] - assert isinstance(method, types.StringTypes), \ - "Invalid method type: %s" % type(method) + assert isinstance(method, str), "Invalid method type: %s" % type(method) params = req.get("params", []) - assert isinstance(params, (types.ListType, types.TupleType)), \ - "Invalid params type: %s" % type(params) + assert isinstance(params, (list, tuple)), "Invalid params type: %s" % type(params) except Exception as e: log.msg("Bad Request: %s" % str(e)) raise HTTPError(400) diff --git a/cyclone/mail.py b/cyclone/mail.py index 2ac1fafb00..e362d886a4 100644 --- a/cyclone/mail.py +++ b/cyclone/mail.py @@ -25,14 +25,15 @@ import types import os.path -from cStringIO import StringIO +from io import StringIO from OpenSSL.SSL import OP_NO_SSLv3 -from email import Encoders -from email.MIMEText import MIMEText -from email.MIMEBase import MIMEBase -from email.MIMEMultipart import MIMEMultipart -from email.Utils import COMMASPACE, formatdate +from email import encoders as email_encoders +from email.mime import text as email_mime_text +from email.mime import base as email_mime_base +from email.mime import multipart as email_mime_multipart +from email import utils as email_utils + from twisted.internet import reactor from twisted.internet.defer import Deferred @@ -69,14 +70,14 @@ def __init__(self, from_addr, to_addrs, subject, message, self.subject = subject self.from_addr = from_addr - if isinstance(to_addrs, types.StringType): + if isinstance(to_addrs, str): self.to_addrs = [to_addrs] else: self.to_addrs = to_addrs self.msg = None self.__cache = None - self.message = MIMEText(message, _charset=charset) + self.message = email_mime_text.MIMEText(message, _charset=charset) self.message.set_type(mime) def attach(self, filename, mime=None, charset=None, content=None): @@ -95,13 +96,13 @@ def attach(self, filename, mime=None, charset=None, content=None): fd = open(filename) content = fd.read() fd.close() - elif not isinstance(content, types.StringType): + elif not isinstance(content, str): raise TypeError("Don't know how to attach content: %s" % repr(content)) - part = MIMEBase("application", "octet-stream") + part = email_mime_base.MIMEBase("application", "octet-stream") part.set_payload(content) - Encoders.encode_base64(part) + email_encoders.encode_base64(part) part.add_header("Content-Disposition", "attachment", filename=base) @@ -112,7 +113,7 @@ def attach(self, filename, mime=None, charset=None, content=None): part.set_charset(charset) if self.msg is None: - self.msg = MIMEMultipart() + self.msg = email_mime_multipart.MIMEMultipart() self.msg.attach(self.message) self.msg.attach(part) @@ -126,8 +127,8 @@ def render(self): self.msg["Subject"] = self.subject self.msg["From"] = self.from_addr - self.msg["To"] = COMMASPACE.join(self.to_addrs) - self.msg["Date"] = formatdate(localtime=True) + self.msg["To"] = email_utils.COMMASPACE.join(self.to_addrs) + self.msg["Date"] = email_utils.formatdate(localtime=True) if self.__cache is None: self.__cache = self.msg.as_string() @@ -163,7 +164,7 @@ def sendmail(mailconf, message): d = mail.sendmail(mailconf, msg) d.addCallback(on_response) """ - if not isinstance(mailconf, types.DictType): + if not isinstance(mailconf, dict): raise TypeError("mailconf must be a regular python dictionary") if not isinstance(message, Message): @@ -171,10 +172,10 @@ def sendmail(mailconf, message): host = mailconf.get("host") - if isinstance(host, unicode): - host = str(unicode) + #if isinstance(host, unicode): + # host = str(unicode) - if not isinstance(host, types.StringType): + if not isinstance(host, str): raise ValueError("mailconf requires a 'host' configuration") use_tls = mailconf.get("tls") @@ -186,7 +187,7 @@ def sendmail(mailconf, message): port = mailconf.get("port", 25) contextFactory = None - if not isinstance(port, types.IntType): + if not isinstance(port, int): raise ValueError("mailconf requires a proper 'port' configuration") result = Deferred() diff --git a/cyclone/options.py b/cyclone/options.py index 9fe8319c2c..0ab4baf1ba 100644 --- a/cyclone/options.py +++ b/cyclone/options.py @@ -124,7 +124,7 @@ def parse_command_line(self, args=None): if args is None: args = sys.argv remaining = [] - for i in xrange(1, len(args)): + for i in range(1, len(args)): # All things after the last option are command line arguments if not args[i].startswith("-"): remaining = args[i:] @@ -159,7 +159,8 @@ def parse_command_line(self, args=None): def parse_config_file(self, path): config = {} - execfile(path, config, config) + with open(path, 'rb') as f: + exec(compile(f.read(), path, 'exec'), config, config) for name in config: if name in self: self[name].set(config[name]) @@ -193,7 +194,7 @@ def print_help(self, file=sys.stdout): class _Option(object): - def __init__(self, name, default=None, type=basestring, help=None, + def __init__(self, name, default=None, type=str, help=None, metavar=None, multiple=False, file_name=None, group_name=None): if default is None and multiple: @@ -216,12 +217,12 @@ def parse(self, value): datetime.datetime: self._parse_datetime, datetime.timedelta: self._parse_timedelta, bool: self._parse_bool, - basestring: self._parse_string, + str: self._parse_string, }.get(self.type, self.type) if self.multiple: self._value = [] for part in value.split(","): - if self.type in (int, long): + if isnumeric(self): # allow ranges of the form X:Y (inclusive at both ends) lo, _, hi = part.partition(":") lo = _parse(lo) @@ -413,25 +414,25 @@ def __init__(self, color, *args, **kwargs): fg_color = (curses.tigetstr("setaf") or curses.tigetstr("setf") or "") if (3, 0) < sys.version_info < (3, 2, 3): - fg_color = unicode(fg_color, "ascii") + fg_color = str(fg_color, "ascii") self._colors = { - logging.DEBUG: unicode(curses.tparm(fg_color, 4), # Blue + logging.DEBUG: str(curses.tparm(fg_color, 4), # Blue "ascii"), - logging.INFO: unicode(curses.tparm(fg_color, 2), # Green + logging.INFO: str(curses.tparm(fg_color, 2), # Green "ascii"), - logging.WARNING: unicode(curses.tparm(fg_color, 3), # Yellow + logging.WARNING: str(curses.tparm(fg_color, 3), # Yellow "ascii"), - logging.ERROR: unicode(curses.tparm(fg_color, 1), # Red + logging.ERROR: str(curses.tparm(fg_color, 1), # Red "ascii"), } - self._normal = unicode(curses.tigetstr("sgr0"), "ascii") + self._normal = str(curses.tigetstr("sgr0"), "ascii") def format(self, record): try: record.message = record.getMessage() except Exception as e: record.message = "Bad message (%r): %r" % (e, record.__dict__) - assert isinstance(record.message, basestring) # guaranteed by logging + assert isinstance(record.message, str) # guaranteed by logging record.asctime = time.strftime( "%y%m%d %H:%M:%S", self.converter(record.created)) prefix = '[%(levelname)1.1s %(asctime)s %(module)s:%(lineno)d]' % \ diff --git a/cyclone/redis.py b/cyclone/redis.py index c1d2e213e1..3e5fc1ac4b 100644 --- a/cyclone/redis.py +++ b/cyclone/redis.py @@ -83,7 +83,7 @@ def list_or_args(command, keys, args): oldapi = bool(args) try: iter(keys) - if isinstance(keys, (str, unicode)): + if isinstance(keys, (str,)): keys = [keys] if not oldapi: return keys @@ -287,7 +287,7 @@ def lineReceived(self, line): if token == "$": # bulk data try: - self.bulk_length = long(data) + self.bulk_length = int(data) except ValueError: self.replyReceived(InvalidResponse("Cannot convert data " "'%s' to integer" % data)) @@ -301,7 +301,7 @@ def lineReceived(self, line): elif token == "*": # multi-bulk data try: - n = long(data) + n = int(data) except (TypeError, ValueError): self.multi_bulk = MultiBulkStorage() self.replyReceived(InvalidResponse("Cannot convert " @@ -457,7 +457,7 @@ def execute_command(self, *args, **kwargs): for s in args: if isinstance(s, str): cmd = s - elif isinstance(s, unicode): + elif isinstance(s, str): if self.charset is None: raise InvalidData("Encoding charset was not specified") try: @@ -745,7 +745,7 @@ def bitop(self, operation, destkey, *srckeys): srclen = len(srckeys) if srclen == 0: return defer.fail(RedisError("no ``srckeys`` specified")) - if isinstance(operation, (str, unicode)): + if isinstance(operation, str): operation = operation.upper() elif operation is operator.and_ or operation is operator.__and__: operation = 'AND' @@ -895,7 +895,7 @@ def blpop(self, keys, timeout=0): """ Blocking LPOP """ - if isinstance(keys, (str, unicode)): + if isinstance(keys, str): keys = [keys] else: keys = list(keys) @@ -907,7 +907,7 @@ def brpop(self, keys, timeout=0): """ Blocking RPOP """ - if isinstance(keys, (str, unicode)): + if isinstance(keys, str): keys = [keys] else: keys = list(keys) @@ -1235,7 +1235,7 @@ def _zaggregate(self, command, dstkey, keys, aggregate): aggregate = 'SUM' else: err_flag = True - if isinstance(aggregate, (str, unicode)): + if isinstance(aggregate, str): aggregate_u = aggregate.upper() if aggregate_u in ('MIN', 'MAX', 'SUM'): aggregate = aggregate_u @@ -1307,7 +1307,7 @@ def hdel(self, key, fields): """ Remove the specified field or fields from a hash """ - if isinstance(fields, (str, unicode)): + if isinstance(fields, str): fields = [fields] else: fields = list(fields) @@ -1382,7 +1382,7 @@ def watch(self, keys): self.inMulti = False self.unwatch_cc = self._clear_txstate self.commit_cc = lambda: () - if isinstance(keys, (str, unicode)): + if isinstance(keys, str): keys = [keys] d = self.execute_command("WATCH", *keys).addCallback(self._tx_started) return d @@ -1643,7 +1643,7 @@ def dataReceived(self, data, unpause=False): self._reader.feed(data) res = self._reader.gets() while res is not False: - if isinstance(res, basestring): + if isinstance(res, str): res = self.tryConvertData(res) elif isinstance(res, list): res = map(self.tryConvertData, res) @@ -1698,22 +1698,22 @@ def replyReceived(self, reply): self.replyQueue.put(reply) def subscribe(self, channels): - if isinstance(channels, (str, unicode)): + if isinstance(channels, str): channels = [channels] return self.execute_command("SUBSCRIBE", *channels) def unsubscribe(self, channels): - if isinstance(channels, (str, unicode)): + if isinstance(channels, str): channels = [channels] return self.execute_command("UNSUBSCRIBE", *channels) def psubscribe(self, patterns): - if isinstance(patterns, (str, unicode)): + if isinstance(patterns, str): patterns = [patterns] return self.execute_command("PSUBSCRIBE", *patterns) def punsubscribe(self, patterns): - if isinstance(patterns, (str, unicode)): + if isinstance(patterns, str): patterns = [patterns] return self.execute_command("PUNSUBSCRIBE", *patterns) @@ -1855,7 +1855,7 @@ def __init__(self, nodes=[], replicas=160): def add_node(self, node): self.nodes.append(node) - for x in xrange(self.replicas): + for x in range(self.replicas): crckey = zlib.crc32("%s:%d" % (node._factory.uuid, x)) self.ring[crckey] = node self.sorted_keys.append(crckey) @@ -1864,7 +1864,7 @@ def add_node(self, node): def remove_node(self, node): self.nodes.remove(node) - for x in xrange(self.replicas): + for x in range(self.replicas): crckey = zlib.crc32("%s:%d" % (node, x)) self.ring.remove(crckey) self.sorted_keys.remove(crckey) @@ -1919,7 +1919,7 @@ def disconnect(self): def _wrap(self, method, *args, **kwargs): try: key = args[0] - assert isinstance(key, (str, unicode)) + assert isinstance(key, str) except: raise ValueError( "Method '%s' requires a key as the first argument" % method) @@ -1980,9 +1980,6 @@ def __repr__(self): class ShardedUnixConnectionHandler(ShardedConnectionHandler): - def pipeline(self): - pass - def __repr__(self): nodes = [] for conn in self._ring.nodes: @@ -2007,7 +2004,7 @@ def __init__(self, uuid, dbid, poolsize, isLazy=False, raise ValueError("Redis poolsize must be an integer, not %s" % repr(poolsize)) - if not isinstance(dbid, (int, types.NoneType)): + if not isinstance(dbid, (int, None)): raise ValueError("Redis dbid must be an integer, not %s" % repr(dbid)) @@ -2116,7 +2113,7 @@ def makeConnection(host, port, dbid, poolsize, reconnect, isLazy, factory = RedisFactory(uuid, dbid, poolsize, isLazy, ConnectionHandler, charset, password, replyTimeout, convertNumbers) factory.continueTrying = reconnect - for x in xrange(poolsize): + for x in range(poolsize): reactor.connectTCP(host, port, factory, connectTimeout) if isLazy: @@ -2228,7 +2225,7 @@ def makeUnixConnection(path, dbid, poolsize, reconnect, isLazy, factory = RedisFactory(path, dbid, poolsize, isLazy, UnixConnectionHandler, charset, password, replyTimeout, convertNumbers) factory.continueTrying = reconnect - for x in xrange(poolsize): + for x in range(poolsize): reactor.connectUNIX(path, factory, connectTimeout) if isLazy: diff --git a/cyclone/sse.py b/cyclone/sse.py index 5f8b2e26b1..84fc3d0825 100644 --- a/cyclone/sse.py +++ b/cyclone/sse.py @@ -56,9 +56,9 @@ def sendEvent(self, message, event=None, eid=None, retry=None): """ if isinstance(message, dict): message = escape.json_encode(message) - if isinstance(message, unicode): + if isinstance(message, str): message = message.encode("utf-8") - assert isinstance(message, str) + assert isinstance(message, bytes) if eid: self.transport.write("id: %s\n" % eid) diff --git a/cyclone/template.py b/cyclone/template.py index 9de7d8a6b4..05d27a9ac2 100644 --- a/cyclone/template.py +++ b/cyclone/template.py @@ -196,22 +196,15 @@ def add(x, y): import sys import threading import traceback - -if (sys.version_info >= (3, 0)): - from io import StringIO -else: - from cStringIO import StringIO +from io import StringIO from cyclone import escape from cyclone.util import ObjectDict from cyclone.util import bytes_type from cyclone.util import unicode_type -from cyclone.util import list_type -from cyclone.util import exec_in from twisted.python.failure import Failure from twisted.internet.defer import Deferred - _DEFAULT_AUTOESCAPE = "xhtml_escape" _UNSET = object() @@ -240,8 +233,7 @@ def __init__(self, template_string, name="", loader=None, self.file = _File(self, _parse(reader, self)) self.code = self._generate_python(loader, compress_whitespace) except ParseError as e: - raise TemplateError("Error parsing template %s, line %d: %s" % - (name, reader.line, str(e))) + raise TemplateError("Error parsing template %s, line %d: %s" %(name, reader.line, str(e))) self.loader = loader try: @@ -253,7 +245,7 @@ def __init__(self, template_string, name="", loader=None, "exec") except Exception: raise TemplateError("Error compiling template " + name + ":\n" + - _format_code(self.code).rstrip()) + _format_code(self.code).rstrip()) def generate(self, **kwargs): """Generate this template with the given arguments.""" @@ -274,7 +266,7 @@ def generate(self, **kwargs): } namespace.update(self.namespace) namespace.update(kwargs) - exec_in(self.compiled, namespace) + exec(self.compiled, namespace, namespace) execute = namespace["_execute"] # Clear the traceback module's cache of source data now that # we've generated a new template (mainly for this module's @@ -290,9 +282,8 @@ def generate(self, **kwargs): if isinstance(rv, Failure): rv.raiseException() return rv - except: - raise TemplateError("Error executing template " + self.name + - ":\n" + _format_code(traceback.format_exception(*sys.exc_info()))) + except Exception: + raise TemplateError("Error executing template " + self.name + ":\n" + _format_code(traceback.format_exception(*sys.exc_info()))) def _generate_python(self, loader, compress_whitespace): buffer = StringIO() @@ -324,6 +315,7 @@ def _get_ancestors(self, loader): ancestors.extend(template._get_ancestors(loader)) return ancestors + class BaseLoader(object): """Base class for template loaders.""" def __init__(self, autoescape=_DEFAULT_AUTOESCAPE, namespace=None): @@ -430,6 +422,7 @@ def maybe_deferred(self, varName, writer): with writer.indent(): writer.write_line("%s = yield %s" % (varName, varName), self.line) + class _File(_Node): def __init__(self, template, body): self.template = template @@ -494,6 +487,7 @@ def find_named_blocks(self, loader, named_blocks): named_blocks[self.name].append(self) _Node.find_named_blocks(self, loader, named_blocks) + class _ExtendsBlock(_Node): def __init__(self, name): self.name = name @@ -520,6 +514,7 @@ def generate(self, writer): preParent = blocks[idx-1] preParent.generate(writer, force_self=True) + class _IncludeBlock(_Node): def __init__(self, name, reader, line): self.name = name @@ -554,8 +549,7 @@ def generate(self, writer): writer.write_line("_append = _buffer.append", self.line) self.body.generate(writer) writer.write_line("return _utf8('').join(_buffer)", self.line) - writer.write_line("_append(_utf8(%s(%s())))" % ( - self.method, method_name), self.line) + writer.write_line("_append(_utf8(%s(%s())))" % (self.method, method_name), self.line) class _ControlBlock(_Node): @@ -583,8 +577,8 @@ def __init__(self, statement, line): def generate(self, writer): # In case the previous block was empty writer.write_line("pass", self.line) - writer.write_line("%s:" % self.statement, self.line, - writer.indent_size() - 1) + writer.write_line("%s:" % self.statement, self.line, writer.indent_size() - 1) + class _Statement(_Node): def __init__(self, statement, line): @@ -750,7 +744,7 @@ def __str__(self): def _format_code(code): - lines = code if isinstance(code, list_type) else code.splitlines() + lines = code if isinstance(code, list) else code.splitlines() format = "%%%dd %%s\n" % len(repr(len(lines) + 1)) return "".join([format % (i + 1, line) for (i, line) in enumerate(lines)]) diff --git a/cyclone/testing/client.py b/cyclone/testing/client.py index d02bfc8149..35718fce39 100644 --- a/cyclone/testing/client.py +++ b/cyclone/testing/client.py @@ -19,12 +19,7 @@ import urllib from twisted.test import proto_helpers from twisted.internet.defer import inlineCallbacks, returnValue - -try: - from http.cookies import SimpleCookie -except ImportError: - # python 2 compatibility - from Cookie import SimpleCookie +from http.cookies import SimpleCookie class DecodingSimpleCookie(SimpleCookie): @@ -94,10 +89,10 @@ def head(self, uri, params=None, version="HTTP/1.0", headers=None, def request(self, method, uri, *args, **kwargs): params = kwargs.pop("params", {}) or {} if method in ["GET", "HEAD", "OPTIONS"] and params: - uri = uri + "?" + urllib.urlencode(params) + uri = uri + "?" + urllib.parse.urlencode(params) elif method in ["POST", "PATCH", "PUT"]\ and params and not kwargs['body']: - kwargs['body'] = urllib.urlencode(params) + kwargs['body'] = urllib.parse.urlencode(params) connection = kwargs.pop('connection') if not connection: connection = HTTPConnection() @@ -120,14 +115,14 @@ def request(self, method, uri, *args, **kwargs): def setup_response(): headers = HTTPHeaders() - for line in handler._generate_headers().split("\r\n"): - if line.startswith("HTTP") or not line.strip(): + for line in handler._generate_headers().split(b"\r\n"): + if line.startswith(b"HTTP") or not line.strip(): continue headers.parse_line(line) for cookie in headers.get_list("Set-Cookie"): self.cookies.load(cookie) response_body = connection.transport.io.getvalue() - handler.content = response_body.split("\r\n\r\n", 1)[1] + handler.content = response_body.split(b"\r\n\r\n", 1)[1] handler.headers = headers if handler._finished: diff --git a/cyclone/tests/test_app.py b/cyclone/tests/test_app.py index 98fb238e2f..fa5c841623 100644 --- a/cyclone/tests/test_app.py +++ b/cyclone/tests/test_app.py @@ -18,4 +18,4 @@ class AppTests(unittest.TestCase): def test_something(self): - pass + pass \ No newline at end of file diff --git a/cyclone/tests/test_auth.py b/cyclone/tests/test_auth.py index e8b33845cb..e0f98c16a2 100644 --- a/cyclone/tests/test_auth.py +++ b/cyclone/tests/test_auth.py @@ -15,19 +15,13 @@ # License for the specific language governing permissions and limitations # under the License. +from urllib import parse as urllib_parse import cyclone.web -from cyclone.auth import FacebookGraphMixin from twisted.trial import unittest - -try: - # py3 - import urllib.parse as urllib_parse - from unittest.mock import MagicMock, patch -except ImportError: - # py2 - from mock import MagicMock, patch - import urllib as urllib_parse +from twisted.internet import defer +from cyclone.auth import FacebookGraphMixin +from unittest.mock import patch, MagicMock class TestHandler(cyclone.web.RequestHandler, diff --git a/cyclone/tests/test_db.py b/cyclone/tests/test_db.py index b4de516a9f..4cc0a94c59 100644 --- a/cyclone/tests/test_db.py +++ b/cyclone/tests/test_db.py @@ -13,21 +13,17 @@ # License for the specific language governing permissions and limitations # under the License. -from cyclone.sqlite import InlineSQLite from twisted.trial import unittest - -try: - from mock import Mock -except ImportError: - from unittest.mock import Mock +from cyclone.sqlite import InlineSQLite +from unittest.mock import Mock class InlineSQLiteTest(unittest.TestCase): def setUp(self): self.isq = InlineSQLite() self.isq.runOperation( - "create table foobar_test (val1 string, val2 string)") - self.isq.runOperation('insert into foobar_test values ("a", "b")') + "create table `nothing` (val1 string, val2 string)") + self.isq.runOperation('insert into `nothing` values ("a", "b")') def test_init(self): self.assertTrue(hasattr(self.isq, "autoCommit")) @@ -41,16 +37,16 @@ def test_runQuery(self): self.assertEqual(res, [1, 2, 3]) def test_runOperation(self): - self.isq.runOperation('insert into foobar_test values ("c", "d")') - res = self.isq.runQuery("select count(*) from foobar_test") + self.isq.runOperation('insert into `nothing` values ("c", "d")') + res = self.isq.runQuery("select count(*) from `nothing`") self.assertEqual(res[0][0], 2) def test_runOperationMany(self): self.isq.runOperationMany( - 'insert into foobar_test values (?, ?)', + 'insert into `nothing` values (?, ?)', [["a", "b"], ["c", "d"]] ) - res = self.isq.runQuery("select count(*) from foobar_test") + res = self.isq.runQuery("select count(*) from `nothing`") self.assertEqual(res[0][0], 3) def test_commit(self): diff --git a/cyclone/tests/test_escape.py b/cyclone/tests/test_escape.py index 2d05af09a6..1cf5f6e311 100644 --- a/cyclone/tests/test_escape.py +++ b/cyclone/tests/test_escape.py @@ -13,21 +13,24 @@ # License for the specific language governing permissions and limitations # under the License. -from cyclone import escape from twisted.trial import unittest +from unittest.mock import Mock + +from cyclone import escape class TestEscape(unittest.TestCase): + def test_xhtml(self): self.assertEqual( - escape.xhtml_escape("abc42"), - "abc42" + escape.xhtml_escape("abc42"), + "abc42" ) self.assertEqual( - escape.xhtml_escape("<>"), - "<>" + escape.xhtml_escape("<>"), + "<>" ) self.assertEqual( - escape.xhtml_escape("\"'"), - ""'" - ) + escape.xhtml_escape("\"'"), + ""'" + ) \ No newline at end of file diff --git a/cyclone/tests/test_httpclient.py b/cyclone/tests/test_httpclient.py index 13358c9bed..d582566d0c 100644 --- a/cyclone/tests/test_httpclient.py +++ b/cyclone/tests/test_httpclient.py @@ -13,20 +13,16 @@ # License for the specific language governing permissions and limitations # under the License. +from twisted.trial import unittest +from cyclone.httpclient import StringProducer, Receiver, HTTPClient, fetch import cyclone.httpclient +from io import StringIO +from twisted.internet.defer import inlineCallbacks, Deferred, succeed, fail +from unittest.mock import Mock +#from unittest import mock import functools - -from cStringIO import StringIO from cyclone import escape -from cyclone.httpclient import StringProducer, Receiver, HTTPClient, fetch from cyclone.web import HTTPError -from twisted.internet.defer import inlineCallbacks, Deferred, succeed, fail -from twisted.trial import unittest - -try: - from mock import Mock -except ImportError: - from unittest.mock import Mock class TestStringProducer(unittest.TestCase): @@ -99,6 +95,7 @@ def test_fetch_basic(self): client = HTTPClient("http://example.com") client.agent = Mock() _response = Mock() + _response.code = 200 _response.headers.getAllRawHeaders.return_value = {} _response.deliverBody = lambda x: x.dataReceived("done") \ or x.connectionLost(None) @@ -113,6 +110,7 @@ def test_fetch_head(self): _response = Mock() _response.headers.getAllRawHeaders.return_value = {} _response.deliverBody = lambda x: x.connectionLost(None) + _response.code = 200 client.agent.request.return_value = succeed(_response) response = yield client.fetch() self.assertEqual(response.body, "") diff --git a/cyclone/tests/test_httpserver.py b/cyclone/tests/test_httpserver.py index 0eb135a8e3..3e614f4f4d 100644 --- a/cyclone/tests/test_httpserver.py +++ b/cyclone/tests/test_httpserver.py @@ -12,23 +12,16 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. - -from cyclone.httpserver import HTTPConnection, HTTPRequest -from io import BytesIO from twisted.internet import address -from twisted.internet import interfaces +from twisted.trial import unittest +from unittest.mock import Mock +from unittest import mock +from cyclone.httpserver import HTTPConnection, HTTPRequest from twisted.internet.defer import Deferred from twisted.test.proto_helpers import StringTransport -from twisted.trial import unittest - -try: - # py3 - import http.cookies as Cookie - from unittest.mock import Mock -except ImportError: - # py2 - import Cookie - from mock import Mock +from twisted.internet import interfaces +from io import BytesIO +from http import cookies as http_cookies class HTTPConnectionTest(unittest.TestCase): @@ -58,27 +51,27 @@ def test_notifyFinish(self): def test_lineReceived(self): self.con.connectionMade() - line = "Header: something" + line = b"Header: something" self.con.lineReceived(line) self.assertTrue(line + self.con.delimiter in self.con._headersbuffer) self.con._on_headers = Mock() - self.con.lineReceived("") - self.con._on_headers.assert_called_with('Header: something\r\n') + self.con.lineReceived(b"") + self.con._on_headers.assert_called_with(b'Header: something\r\n') def test_rawDataReceived(self): self.con.connectionMade() self.con._contentbuffer = BytesIO() self.con._on_request_body = Mock() self.con.content_length = 5 - data = "some data" + data = b"some data" self.con.rawDataReceived(data) - self.con._on_request_body.assert_called_with("some ") + self.con._on_request_body.assert_called_with(b"some ") def test_write(self): self.con.transport = StringTransport() self.con._request = Mock() - self.con.write("data") - self.assertEqual(self.con.transport.io.getvalue(), "data") + self.con.write(b"data") + self.assertEqual(self.con.transport.io.getvalue(), b"data") def test_finish(self): self.con._request = Mock() @@ -146,79 +139,85 @@ def test_finish_request_http1_discon(self): self.con.transport.loseConnection.assert_called_with() def test_on_headers_simple(self): - self.con._remote_ip = Mock() - self.con.request_callback = Mock() - self.con.__dict__['_remote_ip'] = "127.0.0.1" - self.con.connectionMade() - data = \ - "GET / HTTP/1.1\r\n" - self.con._on_headers(data) - self.assertEqual(self.con.request_callback.call_count, 1) + with mock.patch.object(HTTPConnection, '_remote_ip', return_value=None) as m_obj: + self.con = HTTPConnection() + self.con.factory = Mock() + self.con.request_callback = Mock() + self.con._remote_ip = "127.0.0.1" + self.con.connectionMade() + data = b"GET / HTTP/1.1\r\n" + self.con._on_headers(data) + self.assertEqual(self.con.request_callback.call_count, 1) def test_on_headers_invalid(self): - self.con._remote_ip = Mock() - self.con.request_callback = Mock() - self.con.transport = Mock() - self.con.__dict__['_remote_ip'] = "127.0.0.1" - self.con.connectionMade() - data = \ - "GET /" - self.con._on_headers(data) - self.con.transport.loseConnection.assert_called_with() + with mock.patch.object(HTTPConnection, '_remote_ip', return_value=None) as m_obj: + self.con = HTTPConnection() + self.con.factory = Mock() + self.con.request_callback = Mock() + self.con.transport = Mock() + self.con._remote_ip = "127.0.0.1" + self.con.connectionMade() + data = b"GET /" + self.con._on_headers(data) + self.con.transport.loseConnection.assert_called_with() def test_on_headers_invalid_version(self): - self.con._remote_ip = Mock() - self.con.request_callback = Mock() - self.con.transport = Mock() - self.con.__dict__['_remote_ip'] = "127.0.0.1" - self.con.connectionMade() - data = \ - "GET / HTTS/1.1" - self.con._on_headers(data) - self.con.transport.loseConnection.assert_called_with() + with mock.patch.object(HTTPConnection, '_remote_ip', return_value=None) as m_obj: + self.con = HTTPConnection() + self.con.factory = Mock() + self.con.request_callback = Mock() + self.con.transport = Mock() + self.con._remote_ip = "127.0.0.1" + self.con.connectionMade() + data = b"GET / HTTS/1.1" + self.con._on_headers(data) + self.con.transport.loseConnection.assert_called_with() def test_on_headers_content_length(self): - self.con._remote_ip = Mock() - self.con.setRawMode = Mock() - self.con.__dict__['_remote_ip'] = "127.0.0.1" - self.con.connectionMade() - data = \ - "GET / HTTP/1.1\r\n"\ - "Content-Length: 5\r\n"\ - "\r\n" - self.con._on_headers(data) - self.con.setRawMode.assert_called_with() - self.assertEqual(self.con.content_length, 5) + with mock.patch.object(HTTPConnection, '_remote_ip', return_value=None) as m_obj: + self.con = HTTPConnection() + self.con.factory = Mock() + self.con.setRawMode = Mock() + self.con._remote_ip = "127.0.0.1" + self.con.connectionMade() + data = \ + b"GET / HTTP/1.1\r\n" \ + b"Content-Length: 5\r\n" \ + b"\r\n" + self.con._on_headers(data) + self.con.setRawMode.assert_called_with() + self.assertEqual(self.con.content_length, 5) def test_on_headers_continue(self): - self.con._remote_ip = Mock() - self.con.transport = StringTransport() - self.con.setRawMode = Mock() - self.con.__dict__['_remote_ip'] = "127.0.0.1" - self.con.connectionMade() - data = \ - "GET / HTTP/1.1\r\n"\ - "Content-Length: 5\r\n"\ - "Expect: 100-continue"\ - "\r\n" - self.con._on_headers(data) - self.assertEqual( - self.con.transport.io.getvalue().strip(), - "HTTP/1.1 100 (Continue)" - ) + with mock.patch.object(HTTPConnection, '_remote_ip', return_value=None) as m_obj: + self.con = HTTPConnection() + self.con.factory = Mock() + self.con.transport = StringTransport() + self.con.setRawMode = Mock() + self.con._remote_ip = "127.0.0.1" + self.con.connectionMade() + data = \ + b"GET / HTTP/1.1\r\n"\ + b"Content-Length: 5\r\n"\ + b"Expect: 100-continue"\ + b"\r\n" + self.con._on_headers(data) + self.assertEqual(self.con.transport.io.getvalue().strip(), b"HTTP/1.1 100 (Continue)") def test_on_headers_big_body(self): - self.con._remote_ip = Mock() - self.con.transport = StringTransport() - self.con.setRawMode = Mock() - self.con.__dict__['_remote_ip'] = "127.0.0.1" - self.con.connectionMade() - data = \ - "GET / HTTP/1.1\r\n"\ - "Content-Length: 10000000\r\n"\ - "\r\n" - self.con._on_headers(data) - self.assertTrue(self.con._contentbuffer) + with mock.patch.object(HTTPConnection, '_remote_ip', return_value=None) as m_obj: + self.con = HTTPConnection() + self.con.factory = Mock() + self.con.transport = StringTransport() + self.con.setRawMode = Mock() + self.con._remote_ip = "127.0.0.1" + self.con.connectionMade() + data = \ + b"GET / HTTP/1.1\r\n"\ + b"Content-Length: 10000000\r\n"\ + b"\r\n" + self.con._on_headers(data) + self.assertTrue(self.con._contentbuffer) def test_on_request_body_get(self): self.con.request_callback = Mock() @@ -226,7 +225,7 @@ def test_on_request_body_get(self): self.con._request.method = "GET" self.con._request.headers = { } - data = "" + data = b"" self.con._on_request_body(data) self.assertEqual(self.con.request_callback.call_count, 1) @@ -252,14 +251,14 @@ def test_on_request_body_post_multipart_form_data(self): "Content-Type": "multipart/form-data; boundary=AaB03x" } data = \ - "--AaB03x\r\n"\ - 'Content-Disposition: form-data; name="a"\r\n'\ - "\r\n"\ - "b\r\n"\ - "--AaB03x--\r\n" + b"--AaB03x\r\n"\ + b'Content-Disposition: form-data; name="a"\r\n'\ + b"\r\n"\ + b"b\r\n"\ + b"--AaB03x--\r\n" self.con._on_request_body(data) self.assertEqual(self.con.request_callback.call_count, 1) - self.assertEqual(self.con._request.arguments, {"a": ["b"]}) + self.assertEqual(self.con._request.arguments, {"a": [b"b"]}) def test_remote_ip(self): self.con.transport = StringTransport() @@ -356,13 +355,13 @@ def test_cookies_invalid(self): def throw_exc(ignore): raise Exception() - old_cookie = Cookie.SimpleCookie - Cookie.SimpleCookie = Mock() - Cookie.SimpleCookie.return_value.load = throw_exc + old_cookie = http_cookies.SimpleCookie + http_cookies.SimpleCookie = Mock() + http_cookies.SimpleCookie.return_value.load = throw_exc self.req.cookies cookies = self.req.cookies self.assertEqual(cookies, {}) - Cookie.SimpleCookie = old_cookie + http_cookies.SimpleCookie = old_cookie def test_full_url(self): expected = "http://127.0.0.1/something" diff --git a/cyclone/tests/test_mail.py b/cyclone/tests/test_mail.py index 78bb95432a..bf2f4ddd9c 100644 --- a/cyclone/tests/test_mail.py +++ b/cyclone/tests/test_mail.py @@ -12,17 +12,10 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. - -import types - +from twisted.trial import unittest from cyclone.mail import ContextFactory, ClientContextFactory, Message from cyclone.mail import sendmail -from twisted.trial import unittest - -try: - from mock import Mock, patch -except ImportError: - from unittest.mock import Mock, patch +from unittest.mock import Mock, patch class ContextFactoryTest(unittest.TestCase): @@ -54,7 +47,7 @@ def test_init_single_addr(self): "hi thar", "This is a message." ) - self.assertTrue(isinstance(message.to_addrs, types.ListType)) + self.assertTrue(isinstance(message.to_addrs, list)) def test_attach(self): open("foo.txt", "w").write("sometext") diff --git a/cyclone/tests/test_requirements.txt b/cyclone/tests/test_requirements.txt index 803b1afaf2..932a8957f7 100644 --- a/cyclone/tests/test_requirements.txt +++ b/cyclone/tests/test_requirements.txt @@ -1,3 +1 @@ mock -twisted>=12.0 -pyopenssl diff --git a/cyclone/tests/test_template.py b/cyclone/tests/test_template.py index 1d219c9c8d..8811b8adaf 100644 --- a/cyclone/tests/test_template.py +++ b/cyclone/tests/test_template.py @@ -13,17 +13,20 @@ # License for the specific language governing permissions and limitations # under the License. -from cyclone import template from twisted.internet import defer -from twisted.internet import reactor from twisted.trial import unittest +from twisted.internet import reactor + +from unittest.mock import Mock + +from cyclone import template class TestTemplates(unittest.TestCase): def test_simple_var(self): t = template.Template(r"My name is: {{ name }}") - self.assertEqual(t.generate(name="Alice"), "My name is: Alice") - self.assertEqual(t.generate(name="Bob"), "My name is: Bob") + self.assertEqual(t.generate(name="Alice"), b"My name is: Alice") + self.assertEqual(t.generate(name="Bob"), b"My name is: Bob") def test_blocks(self): loader = template.DictLoader({ @@ -60,7 +63,7 @@ def test_blocks(self): def test_if(self): t = template.Template( - r"{% if a == 1 %}One{% elif a < 0 %}Negative{% elif isinstance(a, basestring) %}String{% else %}Unknown{% end %}") + r"{% if isinstance(a, str)%}String{% elif a < 0 %}Negative{% elif a==1 %}One{% else %}Unknown{% end %}") self.assertEqual(t.generate(a=1), b"One") self.assertEqual(t.generate(a=-1), b"Negative") self.assertEqual(t.generate(a=-1.67), b"Negative") @@ -71,48 +74,48 @@ def test_if(self): def test_comment(self): self.assertEqual( - template.Template(r"{% comment blah! %}42").generate(), - b"42" + template.Template(r"{% comment blah! %}42").generate(), + b"42" ) def test_set(self): self.assertEqual( - template.Template(r"{% set x=42 %}{{ val + x }}").generate(val=-42), - "0" + template.Template(r"{% set x=42 %}{{ val + x }}").generate(val=-42), + b"0" ) self.assertEqual( - template.Template(r"{% set x=val2 %}{{ val + x }}").generate(val=1, val2=10), - "11" + template.Template(r"{% set x=val2 %}{{ val + x }}").generate(val=1, val2=10), + b"11" ) def test_loops(self): self.assertEqual( - template.Template(r"{% for x in [1,2,3,4] %}{{ x }}:{% end %}").generate(), - "1:2:3:4:" + template.Template(r"{% for x in [1,2,3,4] %}{{ x }}:{% end %}").generate(), + b"1:2:3:4:" ) self.assertEqual( - template.Template(r"{% set x=0 %}{% while x < 10 %}{{x}};{% set x += 2 %}{% end %}").generate(), - "0;2;4;6;8;" + template.Template(r"{% set x=0 %}{% while x < 10 %}{{x}};{% set x += 2 %}{% end %}").generate(), + b"0;2;4;6;8;" ) def test_autoescape(self): t = template.Template(r"<{{x}}>") self.assertEqual( - t.generate(x="<"), - "<<>" + t.generate(x="<"), + b"<<>" ) self.assertEqual( - t.generate(x=">"), - "<>>" + t.generate(x=">"), + b"<>>" ) t2 = template.Template(r"{% autoescape None %}<{{x}}>") self.assertEqual( - t2.generate(x="<"), - "<<>" + t2.generate(x="<"), + b"<<>" ) self.assertEqual( - t2.generate(x=">"), - "<>>" + t2.generate(x=">"), + b"<>>" ) @defer.inlineCallbacks @@ -128,18 +131,18 @@ def _mkDeferred(rv, delay=None): # Test that template immidiatly resolves deferreds if possible t = template.Template(r"-) {{x}} <-> {{y(63)}} :!") self.assertEqual( - t.generate(x=_mkDeferred(42), y=_mkDeferred), - "-) 42 <-> 63 :!" + t.generate(x=_mkDeferred(42), y=_mkDeferred), + b"-) 42 <-> 63 :!" ) # Test delayed execution d = t.generate( - x=_mkDeferred("hello", 0.1), - y=lambda val: _mkDeferred(val - 60, 0.5) + x=_mkDeferred("hello", 0.1), + y=lambda val: _mkDeferred(val - 60, 0.5) ) self.assertTrue(isinstance(d, defer.Deferred), d) txt = yield d self.assertEqual( - txt, - "-) hello <-> 3 :!" + txt, + b"-) hello <-> 3 :!" ) diff --git a/cyclone/tests/test_testing.py b/cyclone/tests/test_testing.py index d608c7ea20..e05aaf7dd6 100644 --- a/cyclone/tests/test_testing.py +++ b/cyclone/tests/test_testing.py @@ -13,11 +13,11 @@ # License for the specific language governing permissions and limitations # under the License. +from twisted.trial import unittest from cyclone.testing import CycloneTestCase, Client from cyclone.web import Application, RequestHandler, asynchronous from twisted.internet import reactor from twisted.internet.defer import inlineCallbacks -from twisted.trial import unittest class TestHandler(RequestHandler): @@ -87,43 +87,43 @@ def test_create_client(self): @inlineCallbacks def test_get_request(self): response = yield self.client.get("/testing/") - self.assertEqual(response.content, "Something") + self.assertEqual(response.content, b"Something") self.assertTrue(len(response.headers) > 3) @inlineCallbacks def test_get_request_with_params(self): response = yield self.client.get("/testing/", {"q": "query"}) - self.assertEqual(response.content, "Something") + self.assertEqual(response.content, b"Something") self.assertTrue(len(response.headers) > 3) @inlineCallbacks def test_post_request(self): response = yield self.client.post("/testing/") - self.assertEqual(response.content, "Something posted") + self.assertEqual(response.content, b"Something posted") self.assertTrue(len(response.headers) > 3) @inlineCallbacks def test_put_request(self): response = yield self.client.put("/testing/") - self.assertEqual(response.content, "Something put") + self.assertEqual(response.content, b"Something put") self.assertTrue(len(response.headers) > 3) @inlineCallbacks def test_head_request(self): response = yield self.client.head("/testing/") - self.assertEqual(response.content, "") + self.assertEqual(response.content, b"") self.assertTrue(len(response.headers) > 3) @inlineCallbacks def test_delete_request(self): response = yield self.client.delete("/testing/") - self.assertEqual(response.content, "") + self.assertEqual(response.content, b"") self.assertTrue(len(response.headers) > 3) @inlineCallbacks def test_get_deferred_request(self): response = yield self.client.get("/deferred_testing/") - self.assertEqual(response.content, "Something...done!") + self.assertEqual(response.content, b"Something...done!") self.assertTrue(len(response.headers) > 3) @inlineCallbacks @@ -131,8 +131,8 @@ def test_cookies(self): response = yield self.client.get("/cookie_testing/") self.assertEqual( self.client.cookies.get_secure_cookie("test_cookie"), - "test_value" + b"test_value" ) response = yield self.client.post("/cookie_testing/") - self.assertEqual(response.content, "test_value") + self.assertEqual(response.content, b"test_value") diff --git a/cyclone/tests/test_utils.py b/cyclone/tests/test_utils.py index 6b7af3f3fb..89520cce8d 100644 --- a/cyclone/tests/test_utils.py +++ b/cyclone/tests/test_utils.py @@ -13,20 +13,16 @@ # License for the specific language governing permissions and limitations # under the License. -import datetime +from twisted.trial import unittest +from cyclone.escape import xhtml_escape, xhtml_unescape from cyclone.escape import json_encode, json_decode -from cyclone.escape import recursive_unicode, linkify, _convert_entity from cyclone.escape import squeeze, url_escape, url_unescape from cyclone.escape import utf8, to_unicode, to_basestring -from cyclone.escape import xhtml_escape, xhtml_unescape +from cyclone.escape import recursive_unicode, linkify, _convert_entity from cyclone.util import _emit, ObjectDict, import_object -from twisted.trial import unittest - -try: - from mock import Mock -except ImportError: - from unittest.mock import Mock +from unittest.mock import Mock +import datetime class EscapeTest(unittest.TestCase): @@ -57,7 +53,7 @@ def test_url_escape(self): self.assertEqual(url_escape("a value"), "a+value") def test_url_unescape(self): - self.assertEqual(url_unescape("a+value", encoding=None), b"a value") + self.assertEqual(url_unescape("a+value", encoding=None), "a value") self.assertEqual(url_unescape("a+value"), "a value") def test_utf8(self): diff --git a/cyclone/tests/test_web.py b/cyclone/tests/test_web.py index bc5b0514d3..6f994079d6 100644 --- a/cyclone/tests/test_web.py +++ b/cyclone/tests/test_web.py @@ -13,26 +13,18 @@ # License for the specific language governing permissions and limitations # under the License. -import email.utils -import time - -from cyclone.escape import unicode_type -from cyclone.template import DictLoader -from cyclone.web import Application, URLSpec, URLReverseError +from twisted.trial import unittest from cyclone.web import RequestHandler, HTTPError +from cyclone.web import Application, URLSpec, URLReverseError +from cyclone.escape import unicode_type +from unittest.mock import Mock from datetime import datetime +from http import cookies as http_cookies +import email.utils +import calendar +import time from twisted.internet import defer, reactor -from twisted.trial import unittest - -try: - # py3 - import http.cookies as Cookie - from unittest.mock import Mock -except ImportError: - # py2 - import Cookie - from mock import Mock - +from cyclone.template import DictLoader class RequestHandlerTest(unittest.TestCase): def assertHasAttr(self, obj, attr_name): @@ -73,7 +65,7 @@ def test_clear(self): self.rh.clear() self.assertEqual( set(self.rh._headers.keys()), - {"Server", "Content-Type", "Date", "Connection"}, + set(["Server", "Content-Type", "Date", "Connection"]) ) self.assertEqual(self.rh._list_headers, []) @@ -118,7 +110,7 @@ def test_convert_header_value(self): def test_convert_unicode_header_value(self): value = self.rh._convert_header_value(u"Value") self.assertEqual(value, "Value") - self.assertTrue(type(value) != unicode_type) + self.assertTrue(type(value) == unicode_type) def test_convert_unicode_datetime_header_value(self): now = datetime(2014, 4, 4) @@ -187,7 +179,7 @@ def test_set_invalid_cookie(self): ValueError, self.rh.set_cookie, "\x00bbb", "badcookie") def test_set_cookie_already_exists(self): - self.rh._new_cookie = Cookie.SimpleCookie() + self.rh._new_cookie = http_cookies.SimpleCookie() self.rh._new_cookie["name"] = "value" self.rh.set_cookie("name", "value") @@ -251,7 +243,7 @@ def test_write_dict(self): self.rh.write({"foo": "bar"}) self.assertEqual( self.rh._write_buffer, - ['{"foo": "bar"}'] + [b'{"foo": "bar"}'] ) def test_create_template_loader(self): @@ -374,24 +366,24 @@ def test_render_string(self): _mkDeferred = self._mkDeferred self.assertEqual( self.handler.render_string("simple.html", msg="Hello World!"), - "simple: Hello World!" + b"simple: Hello World!" ) self.assertEqual( self.handler.render_string( "simple.html", msg=_mkDeferred("Hello Deferred!")), - "simple: Hello Deferred!" + b"simple: Hello Deferred!" ) d = self.handler.render_string( "simple.html", msg=_mkDeferred("Hello Deferred!", 0.1)) self.assertTrue(isinstance(d, defer.Deferred), d) msg = yield d - self.assertEqual(msg, "simple: Hello Deferred!") + self.assertEqual(msg, b"simple: Hello Deferred!") def test_generate_headers(self): headers = self.handler._generate_headers() self.assertIn( - "HTTP MOCK 200 OK", + b"HTTP MOCK 200 OK", headers, ) @@ -399,14 +391,14 @@ def test_generate_headers(self): def test_simple_handler(self): self.handler.get = lambda: self.handler.finish("HELLO WORLD") page = yield self._execute_request(False) - self.assertEqual(page, "HELLO WORLD") + self.assertEqual(page, b"HELLO WORLD") @defer.inlineCallbacks def test_deferred_handler(self): self.handler.get = lambda: self._mkDeferred( lambda: self.handler.finish("HELLO DEFERRED"), 0.01) page = yield self._execute_request(False) - self.assertEqual(page, "HELLO DEFERRED") + self.assertEqual(page, b"HELLO DEFERRED") @defer.inlineCallbacks def test_deferred_arg_in_render(self): @@ -415,7 +407,7 @@ def test_deferred_arg_in_render(self): "simple.html", msg=templateArg) self.handler.get = handlerGetFn page = yield self._execute_request(False) - self.assertEqual(page, "simple: it works!") + self.assertEqual(page, b"simple: it works!") def setUp(self): self.app = app = Mock() @@ -464,8 +456,7 @@ def _execute_request(self, outputHeaders): handler._headers_written = True handler._execute([]) yield self._onFinishD - - out = "" + out = b"" for (args, kwargs) in self.request.write.call_args_list: self.assertFalse(kwargs) self.assertEqual(len(args), 1) diff --git a/cyclone/util.py b/cyclone/util.py index 87ddfa9d49..f277e55b0a 100644 --- a/cyclone/util.py +++ b/cyclone/util.py @@ -15,8 +15,6 @@ # License for the specific language governing permissions and limitations # under the License. -import sys - from twisted.python import log @@ -25,24 +23,16 @@ def _emit(self, eventDict): if not text: return - # print "hello? '%s'" % repr(text) timeStr = self.formatTime(eventDict['time']) - # fmtDict = {'system': eventDict['system'], - # 'text': text.replace("\n", "\n\t")} - # msgStr = log._safeFormat("[%(system)s] %(text)s\n", fmtDict) - log.util.untilConcludes(self.write, "%s %s\n" % (timeStr, - text.replace("\n", "\n\t"))) - log.util.untilConcludes(self.flush) # Hoorj! + text.replace("\n", "\n\t"))) + log.util.untilConcludes(self.flush) - -# monkey patch, sorry log.FileLogObserver.emit = _emit class ObjectDict(dict): """Makes a dictionary behave like an object.""" - def __getattr__(self, name): try: return self[name] @@ -72,68 +62,10 @@ def import_object(name): else: raise ImportError("No method named %s" % parts[-1]) -# Fake unicode literal support: Python 3.2 doesn't have the u'' marker for -# literal strings, and alternative solutions like "from __future__ import -# unicode_literals" have other problems (see PEP 414). u() can be applied -# to ascii strings that include \u escapes (but they must not contain -# literal non-ascii characters). - -if not isinstance(b'', type('')): - def u(s): - return s - unicode_type = str - basestring_type = str -else: - def u(s): - return s.decode('unicode_escape') - # These names don't exist in py3, so use noqa comments to disable - # warnings in flake8. - unicode_type = unicode # noqa - basestring_type = basestring # noqa - -bytes_type = str - -if sys.version_info < (3,): - unicode_type = unicode - unicode_char_type = unichr - basestring_type = basestring - import types - list_type = types.ListType - dict_type = types.DictType - int_type = types.IntType - tuple_type = types.TupleType - string_type = types.StringType - string_types = types.StringTypes - exec(""" -def raise_exc_info(exc_info): - raise exc_info[0], exc_info[1], exc_info[2] - -def exec_in(code, glob, loc=None): - if isinstance(code, basestring): - # exec(string) inherits the caller's future imports; compile - # the string first to prevent that. - code = compile(code, '', 'exec', dont_inherit=True) - exec code in glob, loc -""") -else: - unicode_type = str - unicode_char_type = chr - basestring_type = str - list_type = list - dict_type = dict - int_type = int - tuple_type = tuple - string_type = str - string_types = str - exec(""" -def raise_exc_info(exc_info): - raise exc_info[1].with_traceback(exc_info[2]) -def exec_in(code, glob, loc=None): - if isinstance(code, str): - code = compile(code, '', 'exec', dont_inherit=True) - exec(code, glob, loc) -""") +bytes_type = bytes +unicode_type = str +basestring_type = str def doctests(): # pragma: no cover import doctest diff --git a/cyclone/web.py b/cyclone/web.py index 9dc26f573c..c2e7b9a17f 100644 --- a/cyclone/web.py +++ b/cyclone/web.py @@ -52,27 +52,9 @@ def get(self): from __future__ import absolute_import, division, with_statement -try: - import Cookie # py2 -except ImportError: - import http.cookies as Cookie # py3 - -try: - import urlparse # py2 -except ImportError: - import urllib.parse as urlparse # py3 - -try: - from urllib import urlencode # py2 -except ImportError: - from urllib.parse import urlencode # py3 - -try: - import httplib # py2 -except ImportError: - import http.client as httplib # py3 - - +#import Cookie +from http import cookies as http_cookies +from http import client as http_client import base64 import binascii import calendar @@ -93,6 +75,9 @@ def get(self): import time import traceback import types +import urllib +#import urlparse +from urllib import parse as urllib_parse import uuid import cyclone @@ -101,10 +86,10 @@ def get(self): from cyclone import locale from cyclone import template from cyclone.escape import utf8, _unicode -from cyclone.util import ObjectDict, import_object, \ - bytes_type, unicode_type, \ - list_type, dict_type, int_type, tuple_type, string_type - +from cyclone.util import ObjectDict +from cyclone.util import bytes_type +from cyclone.util import import_object +from cyclone.util import unicode_type from io import BytesIO from twisted.python import failure from twisted.python import log @@ -255,7 +240,7 @@ def clear(self): self.set_header("Connection", "Keep-Alive") self._write_buffer = [] self._status_code = 200 - self._reason = httplib.responses[200] + self._reason = http_client.responses[200] def set_default_headers(self): """Override this to set HTTP headers at the beginning of the request. @@ -280,7 +265,7 @@ def set_status(self, status_code, reason=None): self._reason = escape.native_str(reason) else: try: - self._reason = httplib.responses[status_code] + self._reason = http_client.responses[status_code] except KeyError: raise ValueError("unknown status code %d", status_code) @@ -316,9 +301,9 @@ def clear_header(self, name): def _convert_header_value(self, value): if isinstance(value, bytes_type): - pass + value = value.decode('utf-8') elif isinstance(value, unicode_type): - value = value.encode("utf-8") + pass elif isinstance(value, numbers.Integral): # return immediately since we know the converted value will be safe return str(value) @@ -413,7 +398,7 @@ def set_cookie(self, name, value, domain=None, expires=None, path="/", # Don't let us accidentally inject bad stuff raise ValueError("Invalid cookie %r: %r" % (name, value)) if not hasattr(self, "_new_cookie"): - self._new_cookie = Cookie.SimpleCookie() + self._new_cookie = http_cookies.SimpleCookie() if name in self._new_cookie: del self._new_cookie[name] self._new_cookie[name] = value @@ -442,7 +427,7 @@ def clear_cookie(self, name, path="/", domain=None): def clear_all_cookies(self): """Deletes all the cookies the user sent with this request.""" - for name in self.request.cookies.iterkeys(): + for name in self.request.cookies.keys(): self.clear_cookie(name) def set_secure_cookie(self, name, value, expires_days=30, **kwargs): @@ -500,18 +485,17 @@ def redirect(self, url, permanent=False, status=None): if status is None: status = 301 if permanent else 302 else: - assert isinstance(status, int_type) and 300 <= status <= 399 + assert isinstance(status, int) and 300 <= status <= 399 self.set_status(status) # Remove whitespace - url = re.sub(r"[\x00-\x20]+", "", utf8(url)) + url = re.sub(r"[\x00-\x20]+", "", url) if not self.request.uri.startswith('/'): request_uri = '' if self.request.uri.startswith('//'): request_uri = '' else: request_uri = self.request.uri - self.set_header("Location", urlparse.urljoin(utf8(request_uri), - url)) + self.set_header("Location", urllib_parse.urljoin(request_uri, url)) self.finish() def write(self, chunk): @@ -530,12 +514,13 @@ def write(self, chunk): http://haacked.com/archive/2008/11/20/\ anatomy-of-a-subtle-json-vulnerability.aspx """ + if self._finished: raise RuntimeError("Cannot write() after finish(). May be caused " "by using async operations without the " "@asynchronous decorator.") - if isinstance(chunk, dict_type) or \ - (self.serialize_lists and isinstance(chunk, list_type)): + if isinstance(chunk, dict) or \ + (self.serialize_lists and isinstance(chunk, list)): chunk = escape.json_encode(chunk) self.set_header("Content-Type", "application/json") chunk = utf8(chunk) @@ -698,7 +683,7 @@ def create_template_loader(self, template_path): def flush(self, include_footers=False): """Flushes the current output buffer to the network.""" - chunk = "".join(self._write_buffer) + chunk = b"".join(self._write_buffer) self._write_buffer = [] if not self._headers_written: @@ -711,7 +696,7 @@ def flush(self, include_footers=False): else: for transform in self._transforms: # pragma: no cover chunk = transform.transform_chunk(chunk, include_footers) - headers = "" + headers = b"" # Ignore the chunk and only write the headers for HEAD requests if self.request.method == "HEAD": @@ -871,6 +856,7 @@ class BaseHandler(CustomErrorPageMixin, cyclone.web.RequestHandler): kwargs['exception'] = exc_info[1] try: # Put the traceback into sys.exc_info() + #raise exc_info[0], exc_info[1], exc_info[2] raise exc_info[0].with_traceback(exc_info[1], exc_info[2]) except Exception: self.finish(self.get_error_html(status_code, **kwargs)) @@ -1135,7 +1121,7 @@ def _execute(self, transforms, *args, **kwargs): def _deferred_handler(self, function, *args, **kwargs): try: result = function(*args, **kwargs) - except: + except Exception as e: return defer.fail(failure.Failure( captureVars=defer.Deferred.debug)) else: @@ -1159,8 +1145,7 @@ def _deferred_handler(self, function, *args, **kwargs): def _execute_handler(self, r, args, kwargs): if not self._finished: args = [self.decode_argument(arg) for arg in args] - kwargs = dict((k, self.decode_argument(v, name=k)) - for (k, v) in kwargs.iteritems()) + kwargs = dict((k, self.decode_argument(v, name=k)) for (k, v) in kwargs.items()) function = getattr(self, self.request.method.lower(), self.default) d = self._deferred_handler(function, *args, **kwargs) d.addCallbacks(self._execute_success, self._execute_failure) @@ -1178,12 +1163,11 @@ def _generate_headers(self): lines = [utf8(self.request.version + " " + str(self._status_code) + " " + reason)] - lines.extend([(utf8(n) + ": " + utf8(v)) for n, v in - itertools.chain(self._headers.items(), self._list_headers)]) + lines.extend([(utf8(n) + b": " + utf8(v)) for n, v in itertools.chain(self._headers.items(), self._list_headers)]) if hasattr(self, "_new_cookie"): for cookie in self._new_cookie.values(): lines.append(utf8("Set-Cookie: " + cookie.OutputString(None))) - return "\r\n".join(lines) + "\r\n\r\n" + return b"\r\n".join(lines) + b"\r\n\r\n" def _log(self): """Logs the current request. @@ -1214,7 +1198,7 @@ def _handle_request_exception(self, e): if e.log_message and self.settings.get("debug") is True: log.msg(str(e)) - if e.status_code not in httplib.responses: + if e.status_code not in http_client.responses: log.msg("Bad HTTP status code: " + repr(e.status_code)) e.status_code = 500 @@ -1435,12 +1419,12 @@ def add_handlers(self, host_pattern, host_handlers): self.handlers.append((re.compile(host_pattern), handlers)) for spec in host_handlers: - if isinstance(spec, tuple_type): + if isinstance(spec, tuple): assert len(spec) in (2, 3) pattern = spec[0] handler = spec[1] - if isinstance(handler, string_type): + if isinstance(handler, str): # import the Module and instantiate the class # Must be a fully qualified name (module.ClassName) try: @@ -1484,7 +1468,7 @@ def _load_ui_methods(self, methods): if isinstance(methods, types.ModuleType): self._load_ui_methods(dict((n, getattr(methods, n)) for n in dir(methods))) - elif isinstance(methods, list_type): + elif isinstance(methods, list): for m in methods: self._load_ui_methods(m) else: @@ -1497,11 +1481,11 @@ def _load_ui_modules(self, modules): if isinstance(modules, types.ModuleType): self._load_ui_modules(dict((n, getattr(modules, n)) for n in dir(modules))) - elif isinstance(modules, list_type): + elif isinstance(modules, list): for m in modules: self._load_ui_modules(m) else: - assert isinstance(modules, dict_type) + assert isinstance(modules, dict) for name, cls in modules.items(): try: if issubclass(cls, UIModule): @@ -1530,7 +1514,7 @@ def __call__(self, request): def unquote(s): if s is None: return s - return escape.url_unescape(s, encoding=None) + return escape.url_unescape(s) # Pass matched groups to the handler. Since # match.groups() includes both named and # unnamed groups,we want to use either groups @@ -1615,8 +1599,7 @@ def __str__(self): if self.log_message: return self.log_message % self.args else: - return self.reason or \ - httplib.responses.get(self.status_code, "Unknown") + return self.reason or http_client.responses.get(self.status_code, "Unknown") class HTTPAuthenticationRequired(HTTPError): @@ -1952,13 +1935,13 @@ def wrapper(self, *args, **kwargs): if self.request.method in ("GET", "HEAD"): url = self.get_login_url() if "?" not in url: - if urlparse.urlsplit(url).scheme: + if urllib_parse.urlsplit(url).scheme: # if login url is absolute, make next absolute too next_url = self.request.full_url() else: next_url = self.request.uri url = "%s?%s" % (url, - urlencode(dict(next=next_url))) + urllib_parse.urlencode(dict(next=next_url))) return self.redirect(url) raise HTTPError(403) return method(self, *args, **kwargs) @@ -2065,7 +2048,7 @@ def embedded_javascript(self): def javascript_files(self): result = [] for f in self._get_resources("javascript_files"): - if isinstance(f, (unicode, bytes_type)): + if isinstance(f, (str, bytes_type)): result.append(f) else: result.extend(f) @@ -2077,7 +2060,7 @@ def embedded_css(self): def css_files(self): result = [] for f in self._get_resources("css_files"): - if isinstance(f, (unicode, bytes_type)): + if isinstance(f, (str, bytes_type)): result.append(f) else: result.extend(f) @@ -2089,9 +2072,11 @@ def html_head(self): def html_body(self): return "".join(self._get_resources("html_body")) + class URLReverseError(Exception): """Error generating reversed URL.""" + class URLSpec(object): """Specifies mappings between URLs and handlers.""" def __init__(self, pattern, handler_class, kwargs=None, name=None): @@ -2142,7 +2127,7 @@ def _find_groups(self): if self.regex.groups != pattern.count('('): # The pattern is too complicated for our simplistic matching, # so we can't support reversing it. - return None, None + return (None, None) pieces = [] for fragment in pattern.split('('): @@ -2153,7 +2138,7 @@ def _find_groups(self): else: pieces.append(fragment) - return ''.join(pieces), self.regex.groups + return (''.join(pieces), self.regex.groups) def reverse(self, *args, **kwargs): if not self._path: @@ -2178,7 +2163,7 @@ def reverse(self, *args, **kwargs): if kwargs: items = list(kwargs.items()) items.sort(key=lambda el: el[0]) - rv += "?" + urlencode(items) + rv += "?" + urllib_parse.urlencode(items) return rv url = URLSpec @@ -2188,7 +2173,7 @@ def _time_independent_equals(a, b): if len(a) != len(b): return False result = 0 - if isinstance(a[0], int_type): # python3 byte strings + if isinstance(a[0], int): for x, y in zip(a, b): result |= x ^ y else: # python2 @@ -2198,8 +2183,8 @@ def _time_independent_equals(a, b): def create_signed_value(secret, name, value): - timestamp = utf8(str(int(time.time()))) - value = base64.b64encode(utf8(value)) + timestamp = str(int(time.time())) + value = base64.b64encode(bytes(value,'utf8')).decode('utf8') signature = _create_signature(secret, name, value, timestamp) value = "|".join([value, timestamp, signature]) return value @@ -2208,7 +2193,7 @@ def create_signed_value(secret, name, value): def decode_signed_value(secret, name, value, max_age_days=31): if not value: return None - parts = utf8(value).split("|") + parts = value.split("|") if len(parts) != 3: return None signature = _create_signature(secret, name, parts[0], parts[1]) @@ -2240,4 +2225,4 @@ def _create_signature(secret, *parts): hash = hmac.new(utf8(secret), digestmod=hashlib.sha1) for part in parts: hash.update(utf8(part)) - return utf8(hash.hexdigest()) + return hash.hexdigest() diff --git a/demos/bottle/bottledemo.py b/demos/bottle/bottledemo.py index d65af14272..53ea628b60 100755 --- a/demos/bottle/bottledemo.py +++ b/demos/bottle/bottledemo.py @@ -63,7 +63,7 @@ def auth_login(cli): try: redis_pwd = yield cli.redisdb.get("cyclone:%s" % usr) - except Exception as e: + except Exception, e: log.msg("Redis failed to get('cyclone:%s'): %s" % (usr, str(e))) raise cyclone.web.HTTPError(503) # Service Unavailable @@ -112,7 +112,7 @@ def xmlrpc_echo(self, text): from twisted.python.logfile import DailyLogFile logFile = DailyLogFile.fromFullPath("server.log") print("Logging to daily log file: server.log") -except Exception as e: +except Exception, e: import sys logFile = sys.stdout diff --git a/demos/email/emaildemo.py b/demos/email/emaildemo.py index fa75e4e6db..b490e293fe 100755 --- a/demos/email/emaildemo.py +++ b/demos/email/emaildemo.py @@ -81,7 +81,7 @@ def post(self): response = yield cyclone.mail.sendmail( self.settings.email_settings, msg) self.render("response.html", title="Success", response=response) - except Exception as e: + except Exception, e: self.render("response.html", title="Failure", response=str(e)) diff --git a/demos/httpauth/httpauthdemo_mongo.py b/demos/httpauth/httpauthdemo_mongo.py index ccd0cc601e..2dd487068f 100755 --- a/demos/httpauth/httpauthdemo_mongo.py +++ b/demos/httpauth/httpauthdemo_mongo.py @@ -62,7 +62,7 @@ def wrapper(self, *args, **kwargs): response = yield self.mongodb.cyclonedb.users.find_one( {"usr": usr, "pwd": pwd}, fields=["usr"]) mongo_usr = response.get("usr") - except Exception as e: + except Exception, e: log.msg("MongoDB failed to find(): %s" % str(e)) raise cyclone.web.HTTPError(503) # Service Unavailable @@ -97,7 +97,7 @@ def post(self): ObjId = yield self.mongodb.cyclonedb.users.update( {"usr": usr}, {"usr": usr, "pwd": pwd}, upsert=True, safe=True) - except Exception as e: + except Exception, e: log.msg("MongoDB failed to upsert(): %s" % str(e)) raise cyclone.web.HTTPError(503) # Service Unavailable diff --git a/demos/redis/redisdemo.py b/demos/redis/redisdemo.py index 5214bc8cd0..f9c9bf81a8 100755 --- a/demos/redis/redisdemo.py +++ b/demos/redis/redisdemo.py @@ -118,7 +118,7 @@ class TextHandler(cyclone.web.RequestHandler, RedisMixin): def get(self, key): try: value = yield self.dbconn.get(key) - except Exception as e: + except Exception, e: log.err("Redis failed to get('%s'): %s" % (key, str(e))) raise cyclone.web.HTTPError(503) @@ -130,7 +130,7 @@ def post(self, key): value = self.get_argument("value") try: yield self.dbconn.set(key, value) - except Exception as e: + except Exception, e: log.err("Redis failed to set('%s', '%s'): %s" % (key, value, str(e))) raise cyclone.web.HTTPError(503) @@ -142,7 +142,7 @@ def post(self, key): def delete(self, key): try: n = yield self.dbconn.delete(key) - except Exception as e: + except Exception, e: log.err("Redis failed to del('%s'): %s" % (key, str(e))) raise cyclone.web.HTTPError(503) @@ -157,7 +157,7 @@ class QueueHandler(cyclone.web.RequestHandler, RedisMixin): def get(self, channels): try: channels = channels.split(",") - except Exception as e: + except Exception, e: log.err("Could not split channel names: %s" % str(e)) raise cyclone.web.HTTPError(400, str(e)) @@ -176,7 +176,7 @@ def post(self, channel): try: n = yield self.dbconn.publish(channel, message.encode("utf-8")) - except Exception as e: + except Exception, e: log.msg("Redis failed to publish('%s', '%s'): %s" % (channel, repr(message), str(e))) raise cyclone.web.HTTPError(503) diff --git a/demos/upload/uploaddemo.py b/demos/upload/uploaddemo.py index 45322a924a..f0f10b1521 100755 --- a/demos/upload/uploaddemo.py +++ b/demos/upload/uploaddemo.py @@ -46,7 +46,7 @@ def __init__(self): if not os.path.exists(settings["repository_path"]): try: os.mkdir(settings["repository_path"]) - except Exception as e: + except Exception, e: print("mkdir failed: %s" % str(e)) sys.exit(1) @@ -80,7 +80,7 @@ def post(self): fp = open(os.path.abspath(fn), "w") fp.write(body) fp.close() - except Exception as e: + except Exception, e: log.msg("Could not write file: %s" % str(e)) raise cyclone.web.HTTPError(500) diff --git a/demos/websocket/chat/chatdemo.py b/demos/websocket/chat/chatdemo.py index c8e9d2667a..90dd069017 100755 --- a/demos/websocket/chat/chatdemo.py +++ b/demos/websocket/chat/chatdemo.py @@ -90,7 +90,7 @@ def send_updates(cls, chat): for waiter in cls.waiters: try: waiter.sendMessage(chat) - except Exception as e: + except Exception, e: log.err("Error sending message. %s" % str(e)) def messageReceived(self, message): diff --git a/setup.py b/setup.py index fcc21e74f0..7aaf53359d 100644 --- a/setup.py +++ b/setup.py @@ -21,57 +21,25 @@ import setuptools from distutils import log -requires = ["twisted"] - -def version_cmp(version1, version2): - """ - Return True if version1 is less greater than version2 - """ - def normalize(v): - return [int(x) for x in re.sub(r'(\.0+)*$','', v).split(".")] - return normalize(version1) < normalize(version2) - -# Avoid installation problems on old RedHat distributions (ex. CentOS 5) -# http://stackoverflow.com/questions/7340784/easy-install-pyopenssl-error -py_version = platform.python_version() - -if (version_cmp(str(py_version), str('2.6'))): - distname, version, _id = platform.dist() -else: - distname, version, _id = platform.linux_distribution() - -is_redhat = distname in ["CentOS", "redhat"] -if is_redhat and version and StrictVersion(version) < StrictVersion('6.0'): - requires.append("pyopenssl==0.12") -else: - requires.append("pyopenssl") - -extra = dict(extras_require={'ssl': requires}) - -try: - from setuptools.command import egg_info - egg_info.write_toplevel_names -except (ImportError, AttributeError): - pass -else: - """ - 'twisted' should not occur in the top_level.txt file as this - triggers a bug in pip that removes all of twisted when a package - with a twisted plugin is removed. - """ - def _top_level_package(name): - return name.split('.', 1)[0] - - def _hacked_write_toplevel_names(cmd, basename, filename): - pkgs = dict.fromkeys( - [_top_level_package(k) - for k in cmd.distribution.iter_distribution_names() - if _top_level_package(k) != "twisted" - ] - ) - cmd.write_file("top-level names", filename, '\n'.join(pkgs) + '\n') - - egg_info.write_toplevel_names = _hacked_write_toplevel_names +CLASSIFIERS = [ + 'Development Status :: 5 - Production/Stable', + 'Environment :: Web Environment', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: Apache Software License', + 'Operating System :: POSIX', + 'Programming Language :: Python', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3 :: Only', + 'Topic :: Internet', + 'Topic :: Utilities', + 'Topic :: Software Development :: Libraries :: Python Modules', + 'Topic :: Internet :: WWW/HTTP', + 'Topic :: Internet :: WWW/HTTP :: WSGI', + 'Topic :: Internet :: WWW/HTTP :: WSGI :: Server', + 'Topic :: Internet :: WWW/HTTP :: Dynamic Content'] setuptools.setup( @@ -90,12 +58,6 @@ def _hacked_write_toplevel_names(cmd, basename, filename): "appskel_foreman.zip", "appskel_signup.zip"]}, scripts=["scripts/cyclone"], - **extra -) - -try: - from twisted.plugin import IPlugin, getPlugins - list(getPlugins(IPlugin)) -except Exception as e: - log.warn("*** Failed to update Twisted plugin cache. ***") - log.warn(str(e)) + install_requires=["twisted==19.2.1","pyOpenSSL==19.0.0"], + classifiers=CLASSIFIERS, +) \ No newline at end of file diff --git a/website/sphinx/deferreds.rst b/website/sphinx/deferreds.rst index a5f4492c68..e5fc75963b 100644 --- a/website/sphinx/deferreds.rst +++ b/website/sphinx/deferreds.rst @@ -215,7 +215,7 @@ This is how you handle it:: def get(self): try: response = yield fetch("http://freegeoip.net/xml/") - except Exception as e: + except Exception, e: raise web.HTTPError(503, str(e)) # Service Unavailable ... diff --git a/website/sphinx/redis.rst b/website/sphinx/redis.rst index f31c7c2c2c..3d0cee642b 100644 --- a/website/sphinx/redis.rst +++ b/website/sphinx/redis.rst @@ -241,7 +241,7 @@ Example: def get(self, key): try: value = yield self.redis_conn.get(key) - except Exception as e: + except Exception, e: log.msg("Redis failed to get('%s'): %s" % (key, str(e))) raise cyclone.web.HTTPError(503) @@ -253,7 +253,7 @@ Example: value = self.get_argument("value") try: yield self.redis_conn.set(key, value) - except Exception as e: + except Exception, e: log.msg("Redis failed to set('%s', '%s'): %s" % (key, value, str(e))) raise cyclone.web.HTTPError(503) @@ -264,7 +264,7 @@ Example: def delete(self, key): try: n = yield self.redis_conn.delete(key) - except Exception as e: + except Exception, e: log.msg("Redis failed to del('%s'): %s" % (key, str(e))) raise cyclone.web.HTTPError(503) @@ -438,7 +438,7 @@ Example: try: v = yield rc.get("foo") print "foo=", v - except Exception as e: + except Exception, e: print "can't get foo:", e # Commit, and get all responses from transaction.