Merge pull request #1361 from ilvalle/py3_fixes_step2

few py3 fixes
This commit is contained in:
mdipierro
2016-06-18 07:48:39 -05:00
committed by GitHub
24 changed files with 194 additions and 168 deletions
+1 -1
View File
@@ -25,7 +25,7 @@ install:
virtualenv --python="$PYENV_ROOT/versions/pypy-$PYPY_VERSION/bin/python" "$HOME/virtualenvs/pypy-$PYPY_VERSION" virtualenv --python="$PYENV_ROOT/versions/pypy-$PYPY_VERSION/bin/python" "$HOME/virtualenvs/pypy-$PYPY_VERSION"
source "$HOME/virtualenvs/pypy-$PYPY_VERSION/bin/activate" source "$HOME/virtualenvs/pypy-$PYPY_VERSION/bin/activate"
fi fi
- if [[ $TRAVIS_PYTHON_VERSION == '3.5' ]]; then pip install --download-cache $HOME/.pip-cache pycrypto; fi; - if [[ $TRAVIS_PYTHON_VERSION == '3.5' ]]; then pip install --download-cache $HOME/.pip-cache pycrypto pg8000 pymysql; fi;
- if [[ $TRAVIS_PYTHON_VERSION != '3.5' ]]; then pip install -e .; fi; - if [[ $TRAVIS_PYTHON_VERSION != '3.5' ]]; then pip install -e .; fi;
before_script: before_script:
+5 -2
View File
@@ -21,7 +21,10 @@ if PY2:
import ConfigParser as configparser import ConfigParser as configparser
from email.MIMEBase import MIMEBase from email.MIMEBase import MIMEBase
from email.Header import Header from email.Header import Header
from email import MIMEMultipart, MIMEText, Encoders, Charset from email import Encoders, Charset
from email.MIMEMultipart import MIMEMultipart
from email.MIMEText import MIMEText
from email.Charset import add_charset, QP as charset_QP
from urllib import FancyURLopener, urlencode, urlopen from urllib import FancyURLopener, urlencode, urlopen
from urllib import quote as urllib_quote, unquote as urllib_unquote from urllib import quote as urllib_quote, unquote as urllib_unquote
from string import maketrans from string import maketrans
@@ -88,7 +91,7 @@ else:
from email.mime.text import MIMEText from email.mime.text import MIMEText
from email import encoders as Encoders from email import encoders as Encoders
from email.header import Header from email.header import Header
from email.charset import Charset from email.charset import Charset, add_charset, QP as charset_QP
from urllib.request import FancyURLopener, urlopen from urllib.request import FancyURLopener, urlopen
from urllib.parse import quote as urllib_quote, unquote as urllib_unquote, urlencode from urllib.parse import quote as urllib_quote, unquote as urllib_unquote, urlencode
import html import html
+5 -5
View File
@@ -18,7 +18,7 @@ import fnmatch
import os import os
import copy import copy
import random import random
from gluon._compat import builtin, PY2 from gluon._compat import builtin, PY2, unicodeT, to_native, basestring
from gluon.storage import Storage, List from gluon.storage import Storage, List
from gluon.template import parse_template from gluon.template import parse_template
from gluon.restricted import restricted, compile2 from gluon.restricted import restricted, compile2
@@ -151,7 +151,7 @@ def LOAD(c=None, f='index', args=None, vars=None,
"infinity" or "continuous" are accepted to reload indefinitely the "infinity" or "continuous" are accepted to reload indefinitely the
component component
""" """
from html import TAG, DIV, URL, SCRIPT, XML from gluon.html import TAG, DIV, URL, SCRIPT, XML
if args is None: if args is None:
args = [] args = []
vars = Storage(vars or {}) vars = Storage(vars or {})
@@ -430,7 +430,7 @@ def build_environment(request, response, session, store_current=True):
__builtins__ = mybuiltin() __builtins__ = mybuiltin()
elif is_pypy: # apply the same hack to pypy too elif is_pypy: # apply the same hack to pypy too
__builtins__ = mybuiltin() __builtins__ = mybuiltin()
else: elif PY2:
__builtins__['__import__'] = builtin.__import__ # WHY? __builtins__['__import__'] = builtin.__import__ # WHY?
environment['request'] = request environment['request'] = request
environment['response'] = response environment['response'] = response
@@ -650,8 +650,8 @@ def run_controller_in(controller, function, environment):
vars = response._vars vars = response._vars
if response.postprocessing: if response.postprocessing:
vars = reduce(lambda vars, p: p(vars), response.postprocessing, vars) vars = reduce(lambda vars, p: p(vars), response.postprocessing, vars)
if isinstance(vars, unicode): if isinstance(vars, unicodeT):
vars = vars.encode('utf8') vars = to_native(vars)
elif hasattr(vars, 'xml') and callable(vars.xml): elif hasattr(vars, 'xml') and callable(vars.xml):
vars = vars.xml() vars = vars.xml()
return vars return vars
+2 -1
View File
@@ -19,6 +19,7 @@ Additions:
- .pickle: application/python-pickle - .pickle: application/python-pickle
- .w2p': application/w2p - .w2p': application/w2p
""" """
from gluon._compat import to_native
__all__ = ['contenttype'] __all__ = ['contenttype']
@@ -842,7 +843,7 @@ def contenttype(filename, default='text/plain'):
""" """
Returns the Content-Type string matching extension of the given filename. Returns the Content-Type string matching extension of the given filename.
""" """
filename=to_native(filename)
i = filename.rfind('.') i = filename.rfind('.')
if i >= 0: if i >= 0:
default = CONTENT_TYPE.get(filename[i:].lower(), default) default = CONTENT_TYPE.get(filename[i:].lower(), default)
+6 -12
View File
@@ -6,15 +6,10 @@
from __future__ import print_function from __future__ import print_function
import re import re
import urllib import urllib
from cgi import escape from gluon._compat import maketrans, urllib_quote, unicodeT, _local_html_escape, to_bytes, to_native, _local_html_escape as escape
from gluon._compat import maketrans, urllib_quote, unicodeT, _local_html_escape, to_bytes from ast import parse as ast_parse
import ast
try:
from ast import parse as ast_parse
import ast
except ImportError: # python 2.5
from compiler import parse
import compiler.ast as ast
""" """
TODO: next version should use MathJax TODO: next version should use MathJax
@@ -950,12 +945,11 @@ def render(text,
if protolinks == "default": if protolinks == "default":
protolinks = protolinks_simple protolinks = protolinks_simple
pp = '\n' if pretty_print else '' pp = '\n' if pretty_print else ''
if isinstance(text, unicodeT): text = to_native(text)
text = text.encode('utf8') if not (isinstance(text, str)):
text = str(text or '') text = str(text or '')
text = regex_backslash.sub(lambda m: m.group(1).translate(ttab_in), text) text = regex_backslash.sub(lambda m: m.group(1).translate(ttab_in), text)
text = text.replace('\x05', '').replace('\r\n', '\n') # concatenate strings separeted by \\n text = text.replace('\x05', '').replace('\r\n', '\n') # concatenate strings separeted by \\n
if URL is not None: if URL is not None:
text = replace_at_urls(text, URL) text = replace_at_urls(text, URL)
+27 -7
View File
@@ -84,7 +84,6 @@ Tornado code inspired by http://thomas.pelletier.im/2010/08/websocket-tornado-re
""" """
from __future__ import print_function from __future__ import print_function
import tornado.httpserver import tornado.httpserver
import tornado.websocket import tornado.websocket
import tornado.ioloop import tornado.ioloop
@@ -92,17 +91,38 @@ import tornado.web
import hmac import hmac
import sys import sys
import optparse import optparse
import urllib
import time import time
import sys
if (sys.version_info[0] == 2):
from urllib import urlencode, urlopen
def to_bytes(obj, charset='utf-8', errors='strict'):
if obj is None:
return None
if isinstance(obj, (bytes, bytearray, buffer)):
return bytes(obj)
if isinstance(obj, unicode):
return obj.encode(charset, errors)
raise TypeError('Expected bytes')
else:
from urllib.request import urlopen
from urllib.parse import urlencode
def to_bytes(obj, charset='utf-8', errors='strict'):
if obj is None:
return None
if isinstance(obj, (bytes, bytearray, memoryview)):
return bytes(obj)
if isinstance(obj, str):
return obj.encode(charset, errors)
raise TypeError('Expected bytes')
listeners, names, tokens = {}, {}, {} listeners, names, tokens = {}, {}, {}
def websocket_send(url, message, hmac_key=None, group='default'): def websocket_send(url, message, hmac_key=None, group='default'):
sig = hmac_key and hmac.new(hmac_key, message).hexdigest() or '' sig = hmac_key and hmac.new(to_bytes(hmac_key), to_bytes(message)).hexdigest() or ''
params = urllib.urlencode( params = urlencode(
{'message': message, 'signature': sig, 'group': group}) {'message': message, 'signature': sig, 'group': group})
f = urllib.urlopen(url, params) f = urlopen(url, to_bytes(params))
data = f.read() data = f.read()
f.close() f.close()
return data return data
@@ -121,7 +141,7 @@ class PostHandler(tornado.web.RequestHandler):
print('%s:MESSAGE to %s:%s' % (time.time(), group, message)) print('%s:MESSAGE to %s:%s' % (time.time(), group, message))
if hmac_key: if hmac_key:
signature = self.request.arguments['signature'][0] signature = self.request.arguments['signature'][0]
if not hmac.new(hmac_key, message).hexdigest() == signature: if not to_bytes(hmac.new(to_bytes(hmac_key), to_bytes(message)).hexdigest()) == signature:
self.send_error(401) self.send_error(401)
for client in listeners.get(group, []): for client in listeners.get(group, []):
client.write_message(message) client.write_message(message)
@@ -140,7 +160,7 @@ class TokenHandler(tornado.web.RequestHandler):
message = self.request.arguments['message'][0] message = self.request.arguments['message'][0]
if hmac_key: if hmac_key:
signature = self.request.arguments['signature'][0] signature = self.request.arguments['signature'][0]
if not hmac.new(hmac_key, message).hexdigest() == signature: if not to_bytes(hmac.new(to_bytes(hmac_key), to_bytes(message)).hexdigest()) == signature:
self.send_error(401) self.send_error(401)
tokens[message] = None tokens[message] = None
+6 -3
View File
@@ -8,7 +8,7 @@
Support for smart import syntax for web2py applications Support for smart import syntax for web2py applications
------------------------------------------------------- -------------------------------------------------------
""" """
from gluon._compat import builtin from gluon._compat import builtin, unicodeT, PY2, to_native
import os import os
import sys import sys
import threading import threading
@@ -47,8 +47,8 @@ def custom_importer(name, globals=None, locals=None, fromlist=None, level=-1):
If the import fails, it falls back on naive_importer If the import fails, it falls back on naive_importer
""" """
if isinstance(name, unicode): if isinstance(name, unicodeT):
name = name.encode('utf8') name = to_native(name)
globals = globals or {} globals = globals or {}
locals = locals or {} locals = locals or {}
@@ -104,6 +104,9 @@ def custom_importer(name, globals=None, locals=None, fromlist=None, level=-1):
finally: finally:
if import_tb: if import_tb:
import_tb = None import_tb = None
elif not(PY2) and level < 0:
# FIXME PY3 why level is < 0?
level = 0
return NATIVE_IMPORTER(name, globals, locals, fromlist, level) return NATIVE_IMPORTER(name, globals, locals, fromlist, level)
+5 -1
View File
@@ -21,6 +21,7 @@ import logging
from gluon.http import HTTP from gluon.http import HTTP
from gzip import open as gzopen from gzip import open as gzopen
from gluon.recfile import generate from gluon.recfile import generate
from gluon._compat import PY2
__all__ = [ __all__ = [
'parse_version', 'parse_version',
@@ -100,7 +101,10 @@ def read_file(filename, mode='r'):
"""Returns content from filename, making sure to close the file explicitly """Returns content from filename, making sure to close the file explicitly
on exit. on exit.
""" """
f = open(filename, mode) if PY2 or 'b' in mode:
f = open(filename, mode)
else:
f = open(filename, mode, encoding="utf8")
try: try:
return f.read() return f.read()
finally: finally:
+2 -2
View File
@@ -13,7 +13,7 @@ Contains the classes for the global used variables:
- Session - Session
""" """
from gluon._compat import pickle, StringIO, copyreg, Cookie, urlparse, PY2, iteritems, to_unicode, to_native from gluon._compat import pickle, StringIO, copyreg, Cookie, urlparse, PY2, iteritems, to_unicode, to_native, unicodeT
from gluon.storage import Storage, List from gluon.storage import Storage, List
from gluon.streamer import streamer, stream_file_or_304_or_206, DEFAULT_CHUNK_SIZE from gluon.streamer import streamer, stream_file_or_304_or_206, DEFAULT_CHUNK_SIZE
from gluon.contenttype import contenttype from gluon.contenttype import contenttype
@@ -569,7 +569,7 @@ class Response(Storage):
if not request: if not request:
request = current.request request = current.request
if isinstance(stream, (str, unicode)): if isinstance(stream, (str, unicodeT)):
stream_file_or_304_or_206(stream, stream_file_or_304_or_206(stream,
chunk_size=chunk_size, chunk_size=chunk_size,
request=request, request=request,
+2 -1
View File
@@ -974,7 +974,8 @@ class DIV(XmlComponent):
""" """
str(COMPONENT) returns COMPONENT.xml() str(COMPONENT) returns COMPONENT.xml()
""" """
return self.xml() # In PY3 __str__ cannot return bytes (TypeError: __str__ returned non-string (type bytes))
return to_native(self.xml())
def flatten(self, render=None): def flatten(self, render=None):
""" """
+13 -19
View File
@@ -18,7 +18,8 @@ import pkgutil
import logging import logging
from cgi import escape from cgi import escape
from threading import RLock from threading import RLock
from gluon._compat import copyreg, PY2, maketrans, iterkeys, unicodeT, to_unicode, to_bytes, iteritems from gluon._compat import copyreg, PY2, maketrans, iterkeys, unicodeT, to_unicode, to_bytes, iteritems, _local_html_escape, to_native, \
pjoin
from gluon.portalocker import read_locked, LockedFile from gluon.portalocker import read_locked, LockedFile
from gluon.utf8 import Utf8 from gluon.utf8 import Utf8
@@ -26,16 +27,12 @@ from gluon.utf8 import Utf8
from gluon.fileutils import listdir from gluon.fileutils import listdir
from gluon.cfs import getcfs from gluon.cfs import getcfs
from gluon.html import XML, xmlescape from gluon.html import XML, xmlescape
if PY2: from gluon.contrib.markmin.markmin2html import render, markmin_escape
# FIXME PY3
from gluon.contrib.markmin.markmin2html import render, markmin_escape
__all__ = ['translator', 'findT', 'update_all_languages'] __all__ = ['translator', 'findT', 'update_all_languages']
ostat = os.stat ostat = os.stat
oslistdir = os.listdir oslistdir = os.listdir
pjoin = os.path.join
pexists = os.path.exists
pdirname = os.path.dirname pdirname = os.path.dirname
isdir = os.path.isdir isdir = os.path.isdir
@@ -167,7 +164,7 @@ def read_dict_aux(filename):
lang_text = read_locked(filename).replace(b'\r\n', b'\n') lang_text = read_locked(filename).replace(b'\r\n', b'\n')
clear_cache(filename) clear_cache(filename)
try: try:
return safe_eval(lang_text) or {} return safe_eval(to_native(lang_text)) or {}
except Exception: except Exception:
e = sys.exc_info()[1] e = sys.exc_info()[1]
status = 'Syntax error in %s (%s)' % (filename, e) status = 'Syntax error in %s (%s)' % (filename, e)
@@ -426,7 +423,7 @@ class lazyT(object):
return len(str(self)) return len(str(self))
def xml(self): def xml(self):
return str(self) if self.M else escape(str(self)) return str(self) if self.M else _local_html_escape(str(self), quote=False)
def encode(self, *a, **b): def encode(self, *a, **b):
return str(self).encode(*a, **b) return str(self).encode(*a, **b)
@@ -623,7 +620,6 @@ class translator(object):
of them matches possible_languages. of them matches possible_languages.
""" """
pl_info = read_possible_languages(self.langpath) pl_info = read_possible_languages(self.langpath)
def set_plural(language): def set_plural(language):
""" """
initialize plural forms subsystem initialize plural forms subsystem
@@ -763,10 +759,10 @@ class translator(object):
symbols = (symbols,) symbols = (symbols,)
symbols = tuple( symbols = tuple(
value if isinstance(value, NUMBERS) value if isinstance(value, NUMBERS)
else xmlescape(value).translate(ttab_in) else to_native(xmlescape(value)).translate(ttab_in)
for value in symbols) for value in symbols)
message = self.params_substitution(message, symbols) message = self.params_substitution(message, symbols)
return XML(message.translate(ttab_out)) return to_native(XML(message.translate(ttab_out)).xml())
def M(self, message, symbols={}, language=None, def M(self, message, symbols={}, language=None,
lazy=None, filter=None, ftag=None, ns=None): lazy=None, filter=None, ftag=None, ns=None):
@@ -800,18 +796,16 @@ class translator(object):
the ## notation is ignored in multiline strings and strings that the ## notation is ignored in multiline strings and strings that
start with ##. This is needed to allow markmin syntax to be translated start with ##. This is needed to allow markmin syntax to be translated
""" """
if isinstance(message, unicodeT): message = to_native(message, 'utf8')
message = message.encode('utf8') prefix = to_native(prefix, 'utf8')
if isinstance(prefix, unicodeT):
prefix = prefix.encode('utf8')
key = prefix + message key = prefix + message
mt = self.t.get(key, None) mt = self.t.get(key, None)
if mt is not None: if mt is not None:
return mt return mt
# we did not find a translation # we did not find a translation
if message.find(to_bytes('##')) > 0: if message.find('##') > 0:
pass pass
if message.find(to_bytes('##')) > 0 and not '\n' in message: if message.find('##') > 0 and not '\n' in message:
# remove comments # remove comments
message = message.rsplit('##', 1)[0] message = message.rsplit('##', 1)[0]
# guess translation same as original # guess translation same as original
@@ -821,7 +815,7 @@ class translator(object):
self.language_file != self.default_language_file: self.language_file != self.default_language_file:
write_dict(self.language_file, self.t) write_dict(self.language_file, self.t)
return regex_backslash.sub( return regex_backslash.sub(
lambda m: m.group(1).translate(ttab_in), str(mt)) lambda m: m.group(1).translate(ttab_in), to_native(mt))
def params_substitution(self, message, symbols): def params_substitution(self, message, symbols):
""" """
@@ -966,7 +960,7 @@ def findT(path, language=DEFAULT_LANGUAGE):
for filename in \ for filename in \
listdir(mp, '^.+\.py$', 0) + listdir(cp, '^.+\.py$', 0)\ listdir(mp, '^.+\.py$', 0) + listdir(cp, '^.+\.py$', 0)\
+ listdir(vp, '^.+\.html$', 0) + listdir(mop, '^.+\.py$', 0): + listdir(vp, '^.+\.html$', 0) + listdir(mop, '^.+\.py$', 0):
data = read_locked(filename) data = to_native(read_locked(filename))
items = regex_translate.findall(data) items = regex_translate.findall(data)
items += regex_translate_m.findall(data) items += regex_translate_m.findall(data)
for item in items: for item in items:
+3 -2
View File
@@ -298,6 +298,7 @@ def wsgibase(environ, responder):
env.web2py_version = web2py_version env.web2py_version = web2py_version
#env.update(global_settings) #env.update(global_settings)
static_file = False static_file = False
http_response = None
try: try:
try: try:
try: try:
@@ -439,8 +440,8 @@ def wsgibase(environ, responder):
gluon.debug.dbg.do_debug(mainpyfile=request.folder) gluon.debug.dbg.do_debug(mainpyfile=request.folder)
serve_controller(request, response, session) serve_controller(request, response, session)
except HTTP as hr:
except HTTP as http_response: http_response = hr
if static_file: if static_file:
return http_response.to(responder, env=env) return http_response.to(responder, env=env)
+2 -2
View File
@@ -11,7 +11,7 @@ import errno
import socket import socket
import logging import logging
import platform import platform
from gluon._compat import iteritems from gluon._compat import iteritems, to_bytes
# Define Constants # Define Constants
VERSION = '1.2.6' VERSION = '1.2.6'
@@ -1772,7 +1772,7 @@ class WSGIWorker(Worker):
if self.chunked: if self.chunked:
self.conn.sendall(b('%x\r\n%s\r\n' % (len(data), data))) self.conn.sendall(b('%x\r\n%s\r\n' % (len(data), data)))
else: else:
self.conn.sendall(data) self.conn.sendall(to_bytes(data))
except socket.timeout: except socket.timeout:
self.closeConnection = True self.closeConnection = True
except socket.error: except socket.error:
+6 -4
View File
@@ -10,7 +10,7 @@ from gluon.html import TAG, XmlComponent, xmlescape
from gluon.languages import lazyT from gluon.languages import lazyT
import gluon.contrib.rss2 as rss2 import gluon.contrib.rss2 as rss2
import json as json_parser import json as json_parser
from gluon._compat import long from gluon._compat import long, to_native, unicodeT
have_yaml = True have_yaml = True
try: try:
@@ -43,7 +43,7 @@ def cast_keys(o, cast=str, encoding="utf-8"):
else: else:
newobj = Storage() newobj = Storage()
for k, v in o.items(): for k, v in o.items():
if (cast == str) and isinstance(k, unicode): if (cast == str) and isinstance(k, unicodeT):
key = k.encode(encoding) key = k.encode(encoding)
else: else:
key = cast(k) key = cast(k)
@@ -83,10 +83,12 @@ def custom_json(o):
return int(o) return int(o)
elif isinstance(o, decimal.Decimal): elif isinstance(o, decimal.Decimal):
return str(o) return str(o)
elif isinstance(o, (bytes, bytearray)):
return str(o)
elif isinstance(o, lazyT): elif isinstance(o, lazyT):
return str(o) return str(o)
elif isinstance(o, XmlComponent): elif isinstance(o, XmlComponent):
return str(o) return to_native(o.xml())
elif isinstance(o, set): elif isinstance(o, set):
return list(o) return list(o)
elif hasattr(o, 'as_list') and callable(o.as_list): elif hasattr(o, 'as_list') and callable(o.as_list):
@@ -159,7 +161,7 @@ def ics(events, title=None, link=None, timeshift=0, calname=True,
return s return s
def safe_encode(text): def safe_encode(text):
if not isinstance(text, (str, unicode)): if not isinstance(text, (str, unicodeT)):
text = str(text) text = str(text)
try: try:
text = text.encode('utf8','replace') text = text.encode('utf8','replace')
+5 -5
View File
@@ -19,7 +19,7 @@ import urllib
import re import re
import os import os
from gluon._compat import StringIO, unichr, urllib_quote, iteritems from gluon._compat import StringIO, unichr, urllib_quote, iteritems, basestring, long, unicodeT
from gluon.http import HTTP, redirect from gluon.http import HTTP, redirect
from gluon.html import XmlComponent, truncate_string from gluon.html import XmlComponent, truncate_string
from gluon.html import XML, SPAN, TAG, A, DIV, CAT, UL, LI, TEXTAREA, BR, IMG from gluon.html import XML, SPAN, TAG, A, DIV, CAT, UL, LI, TEXTAREA, BR, IMG
@@ -1141,7 +1141,7 @@ class SQLFORM(FORM):
# try to retrieve the indicated record using its id # try to retrieve the indicated record using its id
# otherwise ignore it # otherwise ignore it
if record and isinstance(record, (int, long, str, unicode)): if record and isinstance(record, (int, long, str, unicodeT)):
if not str(record).isdigit(): if not str(record).isdigit():
raise HTTP(404, "Object not found") raise HTTP(404, "Object not found")
record = table._db(table._id == record).select().first() record = table._db(table._id == record).select().first()
@@ -1651,7 +1651,7 @@ class SQLFORM(FORM):
original_filename = os.path.split(f)[1] original_filename = os.path.split(f)[1]
elif hasattr(f, 'file'): elif hasattr(f, 'file'):
(source_file, original_filename) = (f.file, f.filename) (source_file, original_filename) = (f.file, f.filename)
elif isinstance(f, (str, unicode)): elif isinstance(f, (str, unicodeT)):
# do not know why this happens, it should not # do not know why this happens, it should not
(source_file, original_filename) = \ (source_file, original_filename) = \
(StringIO(f), 'file.txt') (StringIO(f), 'file.txt')
@@ -3426,7 +3426,7 @@ class ExportClass(object):
""" """
if value is None: if value is None:
return '<NULL>' return '<NULL>'
elif isinstance(value, unicode): elif isinstance(value, unicodeT):
return value.encode('utf8') return value.encode('utf8')
elif isinstance(value, Reference): elif isinstance(value, Reference):
return int(value) return int(value)
@@ -3487,7 +3487,7 @@ class ExporterTSV(ExportClass):
import codecs import codecs
final.write(codecs.BOM_UTF16) final.write(codecs.BOM_UTF16)
writer.writerow( writer.writerow(
[unicode(col).encode("utf8") for col in self.rows.colnames]) [to_unicode(col, "utf8") for col in self.rows.colnames])
data = out.getvalue().decode("utf8") data = out.getvalue().decode("utf8")
data = data.encode("utf-16") data = data.encode("utf-16")
data = data[2:] data = data[2:]
+6 -2
View File
@@ -17,6 +17,7 @@ import re
import errno import errno
from gluon.http import HTTP from gluon.http import HTTP
from gluon.contenttype import contenttype from gluon.contenttype import contenttype
from gluon._compat import PY2
regex_start_range = re.compile('\d+(?=\-)') regex_start_range = re.compile('\d+(?=\-)')
@@ -53,8 +54,11 @@ def stream_file_or_304_or_206(
# if error_message is None: # if error_message is None:
# error_message = rewrite.THREAD_LOCAL.routes.error_message % 'invalid request' # error_message = rewrite.THREAD_LOCAL.routes.error_message % 'invalid request'
try: try:
open = file # this makes no sense but without it GAE cannot open files if PY2:
fp = open(static_file,'rb') open_f = file # this makes no sense but without it GAE cannot open files
else:
open_f = open
fp = open_f(static_file,'rb')
except IOError as e: except IOError as e:
if e[0] == errno.EISDIR: if e[0] == errno.EISDIR:
raise HTTP(403, error_message, web2py_error='file is a directory') raise HTTP(403, error_message, web2py_error='file is a directory')
+3 -4
View File
@@ -263,7 +263,6 @@ class TemplateParser(object):
# This will end up as # This will end up as
# "%s(%s, escape=False)" % (self.writer, value) # "%s(%s, escape=False)" % (self.writer, value)
self.writer = writer self.writer = writer
# Dictionary of custom name lexers to use. # Dictionary of custom name lexers to use.
if isinstance(lexers, dict): if isinstance(lexers, dict):
self.lexers = lexers self.lexers = lexers
@@ -448,7 +447,7 @@ class TemplateParser(object):
fileobj.close() fileobj.close()
except IOError: except IOError:
self._raise_error('Unable to open included view file: ' + filepath) self._raise_error('Unable to open included view file: ' + filepath)
text = to_native(text)
return text return text
def include(self, content, filename): def include(self, content, filename):
@@ -788,7 +787,7 @@ def parse_template(filename,
raise RestrictedError(filename, '', 'Unable to find the file') raise RestrictedError(filename, '', 'Unable to find the file')
else: else:
text = filename.read() text = filename.read()
text = to_native(text)
# Use the file contents to get a parsed template and return it. # Use the file contents to get a parsed template and return it.
return str(TemplateParser(text, context=context, path=path, lexers=lexers, delimiters=delimiters)) return str(TemplateParser(text, context=context, path=path, lexers=lexers, delimiters=delimiters))
@@ -885,7 +884,7 @@ def render(content="hello world",
""" """
# here to avoid circular Imports # here to avoid circular Imports
try: try:
from globals import Response from gluon.globals import Response
except ImportError: except ImportError:
# Working standalone. Build a mock Response object. # Working standalone. Build a mock Response object.
Response = DummyResponse Response = DummyResponse
+6 -6
View File
@@ -14,15 +14,15 @@ from .test_contribs import *
from .test_routes import * from .test_routes import *
from .test_router import * from .test_router import *
from .test_validators import * from .test_validators import *
from .test_tools import *
from .test_utils import *
from .test_serializers import *
from .test_languages import *
from .test_compileapp import *
from .test_appadmin import *
if sys.version[:3] == '2.7': if sys.version[:3] == '2.7':
from .test_compileapp import *
from .test_is_url import * from .test_is_url import *
from .test_languages import *
from .test_serializers import *
from .test_utils import *
from .test_tools import *
from .test_appadmin import *
from .test_scheduler import * from .test_scheduler import *
from .test_web import * from .test_web import *
from .test_old_doctests import * from .test_old_doctests import *
+1
View File
@@ -15,6 +15,7 @@ from .fix_path import fix_sys_path
fix_sys_path(__file__) fix_sys_path(__file__)
from gluon import languages from gluon import languages
from gluon._compat import PY2
MP_WORKING = 0 MP_WORKING = 0
try: try:
+7 -7
View File
@@ -547,7 +547,7 @@ class TestAuth(unittest.TestCase):
def test_basic_blank_forms(self): def test_basic_blank_forms(self):
for f in ['login', 'retrieve_password', 'retrieve_username', 'register']: for f in ['login', 'retrieve_password', 'retrieve_username', 'register']:
html_form = getattr(self.auth, f)().xml() html_form = getattr(self.auth, f)().xml()
self.assertTrue('name="_formkey"' in html_form) self.assertTrue(b'name="_formkey"' in html_form)
for f in ['logout', 'verify_email', 'reset_password', 'change_password', 'profile', 'groups']: for f in ['logout', 'verify_email', 'reset_password', 'change_password', 'profile', 'groups']:
self.assertRaisesRegexp(HTTP, "303*", getattr(self.auth, f)) self.assertRaisesRegexp(HTTP, "303*", getattr(self.auth, f))
@@ -712,7 +712,7 @@ class TestAuth(unittest.TestCase):
self.auth.login_user(self.db(self.db.auth_user.username == 'bart').select().first()) # bypass login_bare() self.auth.login_user(self.db(self.db.auth_user.username == 'bart').select().first()) # bypass login_bare()
self.auth.settings.bulk_register_enabled = True self.auth.settings.bulk_register_enabled = True
bulk_register_form = self.auth.bulk_register(max_emails=10).xml() bulk_register_form = self.auth.bulk_register(max_emails=10).xml()
self.assertTrue('name="_formkey"' in bulk_register_form) self.assertTrue(b'name="_formkey"' in bulk_register_form)
# TODO: def test_manage_tokens(self): # TODO: def test_manage_tokens(self):
# TODO: def test_reset_password(self): # TODO: def test_reset_password(self):
@@ -723,12 +723,12 @@ class TestAuth(unittest.TestCase):
def test_change_password(self): def test_change_password(self):
self.auth.login_user(self.db(self.db.auth_user.username == 'bart').select().first()) # bypass login_bare() self.auth.login_user(self.db(self.db.auth_user.username == 'bart').select().first()) # bypass login_bare()
change_password_form = getattr(self.auth, 'change_password')().xml() change_password_form = getattr(self.auth, 'change_password')().xml()
self.assertTrue('name="_formkey"' in change_password_form) self.assertTrue(b'name="_formkey"' in change_password_form)
def test_profile(self): def test_profile(self):
self.auth.login_user(self.db(self.db.auth_user.username == 'bart').select().first()) # bypass login_bare() self.auth.login_user(self.db(self.db.auth_user.username == 'bart').select().first()) # bypass login_bare()
profile_form = getattr(self.auth, 'profile')().xml() profile_form = getattr(self.auth, 'profile')().xml()
self.assertTrue('name="_formkey"' in profile_form) self.assertTrue(b'name="_formkey"' in profile_form)
# TODO: def test_run_login_onaccept(self): # TODO: def test_run_login_onaccept(self):
# TODO: def test_jwt(self): # TODO: def test_jwt(self):
@@ -766,7 +766,7 @@ class TestAuth(unittest.TestCase):
# basic impersonate() test that return a read form # basic impersonate() test that return a read form
self.assertEqual(self.auth.impersonate().xml(), self.assertEqual(self.auth.impersonate().xml(),
'<form action="#" enctype="multipart/form-data" method="post"><table><tr id="no_table_user_id__row"><td class="w2p_fl"><label class="" for="no_table_user_id" id="no_table_user_id__label">User Id: </label></td><td class="w2p_fw"><input class="integer" id="no_table_user_id" name="user_id" type="text" value="" /></td><td class="w2p_fc"></td></tr><tr id="submit_record__row"><td class="w2p_fl"></td><td class="w2p_fw"><input type="submit" value="Submit" /></td><td class="w2p_fc"></td></tr></table></form>') b'<form action="#" enctype="multipart/form-data" method="post"><table><tr id="no_table_user_id__row"><td class="w2p_fl"><label class="" for="no_table_user_id" id="no_table_user_id__label">User Id: </label></td><td class="w2p_fw"><input class="integer" id="no_table_user_id" name="user_id" type="text" value="" /></td><td class="w2p_fc"></td></tr><tr id="submit_record__row"><td class="w2p_fl"></td><td class="w2p_fw"><input type="submit" value="Submit" /></td><td class="w2p_fc"></td></tr></table></form>')
# bart impersonate itself # bart impersonate itself
self.assertEqual(self.auth.impersonate(bart_id), None) self.assertEqual(self.auth.impersonate(bart_id), None)
self.assertFalse(self.auth.is_impersonating()) # User shouldn't impersonate itself? self.assertFalse(self.auth.is_impersonating()) # User shouldn't impersonate itself?
@@ -776,7 +776,7 @@ class TestAuth(unittest.TestCase):
self.assertTrue(self.auth.is_impersonating()) self.assertTrue(self.auth.is_impersonating())
self.assertEqual(self.auth.user_id, omer_id) # we make it really sure self.assertEqual(self.auth.user_id, omer_id) # we make it really sure
self.assertEqual(impersonate_form.xml(), self.assertEqual(impersonate_form.xml(),
'<form action="#" enctype="multipart/form-data" method="post"><table><tr id="auth_user_id__row"><td class="w2p_fl"><label class="readonly" for="auth_user_id" id="auth_user_id__label">Id: </label></td><td class="w2p_fw"><span id="auth_user_id">2</span></td><td class="w2p_fc"></td></tr><tr id="auth_user_first_name__row"><td class="w2p_fl"><label class="readonly" for="auth_user_first_name" id="auth_user_first_name__label">First name: </label></td><td class="w2p_fw">Omer</td><td class="w2p_fc"></td></tr><tr id="auth_user_last_name__row"><td class="w2p_fl"><label class="readonly" for="auth_user_last_name" id="auth_user_last_name__label">Last name: </label></td><td class="w2p_fw">Simpson</td><td class="w2p_fc"></td></tr><tr id="auth_user_email__row"><td class="w2p_fl"><label class="readonly" for="auth_user_email" id="auth_user_email__label">E-mail: </label></td><td class="w2p_fw">omer@test.com</td><td class="w2p_fc"></td></tr><tr id="auth_user_username__row"><td class="w2p_fl"><label class="readonly" for="auth_user_username" id="auth_user_username__label">Username: </label></td><td class="w2p_fw">omer</td><td class="w2p_fc"></td></tr></table><div style="display:none;"><input name="id" type="hidden" value="2" /></div></form>') b'<form action="#" enctype="multipart/form-data" method="post"><table><tr id="auth_user_id__row"><td class="w2p_fl"><label class="readonly" for="auth_user_id" id="auth_user_id__label">Id: </label></td><td class="w2p_fw"><span id="auth_user_id">2</span></td><td class="w2p_fc"></td></tr><tr id="auth_user_first_name__row"><td class="w2p_fl"><label class="readonly" for="auth_user_first_name" id="auth_user_first_name__label">First name: </label></td><td class="w2p_fw">Omer</td><td class="w2p_fc"></td></tr><tr id="auth_user_last_name__row"><td class="w2p_fl"><label class="readonly" for="auth_user_last_name" id="auth_user_last_name__label">Last name: </label></td><td class="w2p_fw">Simpson</td><td class="w2p_fc"></td></tr><tr id="auth_user_email__row"><td class="w2p_fl"><label class="readonly" for="auth_user_email" id="auth_user_email__label">E-mail: </label></td><td class="w2p_fw">omer@test.com</td><td class="w2p_fc"></td></tr><tr id="auth_user_username__row"><td class="w2p_fl"><label class="readonly" for="auth_user_username" id="auth_user_username__label">Username: </label></td><td class="w2p_fw">omer</td><td class="w2p_fc"></td></tr></table><div style="display:none;"><input name="id" type="hidden" value="2" /></div></form>')
self.auth.logout_bare() self.auth.logout_bare()
# Failing impersonation # Failing impersonation
# User lacking impersonate membership # User lacking impersonate membership
@@ -800,7 +800,7 @@ class TestAuth(unittest.TestCase):
def test_groups(self): def test_groups(self):
self.auth.login_user(self.db(self.db.auth_user.username == 'bart').select().first()) # bypass login_bare() self.auth.login_user(self.db(self.db.auth_user.username == 'bart').select().first()) # bypass login_bare()
self.assertEqual(self.auth.groups().xml(), self.assertEqual(self.auth.groups().xml(),
'<table><tr><td><h3>user_1(1)</h3></td></tr><tr><td><p></p></td></tr></table>') b'<table><tr><td><h3>user_1(1)</h3></td></tr><tr><td><p></p></td></tr></table>')
def test_not_authorized(self): def test_not_authorized(self):
self.current.request.ajax = 'facke_ajax_request' self.current.request.ajax = 'facke_ajax_request'
+3 -3
View File
@@ -12,7 +12,7 @@ from gluon.utils import md5_hash, compare, is_valid_ip_address, web2py_uuid
import hashlib import hashlib
from hashlib import md5, sha1, sha224, sha256, sha384, sha512 from hashlib import md5, sha1, sha224, sha256, sha384, sha512
from gluon.utils import simple_hash, get_digest, secure_dumps, secure_loads from gluon.utils import simple_hash, get_digest, secure_dumps, secure_loads, basestring
class TestUtils(unittest.TestCase): class TestUtils(unittest.TestCase):
@@ -94,8 +94,8 @@ class TestUtils(unittest.TestCase):
secured = secure_dumps(testobj, testkey) secured = secure_dumps(testobj, testkey)
original = secure_loads(secured, testkey) original = secure_loads(secured, testkey)
self.assertEqual(testobj, original) self.assertEqual(testobj, original)
self.assertTrue(isinstance(secured, basestring)) self.assertTrue(isinstance(secured, bytes))
self.assertTrue(':' in secured) self.assertTrue(b':' in secured)
large_testobj = [x for x in range(1000)] large_testobj = [x for x in range(1000)]
secured_comp = secure_dumps(large_testobj, testkey, compression_level=9) secured_comp = secure_dumps(large_testobj, testkey, compression_level=9)
+55 -60
View File
@@ -13,7 +13,8 @@ Auth, Mail, PluginManager and various utilities
import base64 import base64
from functools import reduce from functools import reduce
from gluon._compat import pickle, thread, urllib2, Cookie, StringIO, configparser, MIMEBase, MIMEMultipart, \ from gluon._compat import pickle, thread, urllib2, Cookie, StringIO, configparser, MIMEBase, MIMEMultipart, \
MIMEText, Encoders, Charset, long, urllib_quote, iteritems MIMEText, Encoders, Charset, long, urllib_quote, iteritems, to_bytes, to_native, add_charset, \
charset_QP, basestring, unicodeT, to_unicode
import datetime import datetime
import logging import logging
import sys import sys
@@ -241,6 +242,7 @@ class Mail(object):
if filename is None: if filename is None:
raise Exception('Missing attachment name') raise Exception('Missing attachment name')
payload = payload.read() payload = payload.read()
#FIXME PY3 can be used to_native?
filename = filename.encode(encoding) filename = filename.encode(encoding)
if content_type is None: if content_type is None:
content_type = contenttype(filename) content_type = contenttype(filename)
@@ -248,9 +250,9 @@ class Mail(object):
self.my_payload = payload self.my_payload = payload
MIMEBase.__init__(self, *content_type.split('/', 1)) MIMEBase.__init__(self, *content_type.split('/', 1))
self.set_payload(payload) self.set_payload(payload)
self['Content-Disposition'] = 'attachment; filename="%s"' % filename self['Content-Disposition'] = 'attachment; filename="%s"' % to_native(filename, encoding)
if content_id is not None: if content_id is not None:
self['Content-Id'] = '<%s>' % content_id.encode(encoding) self['Content-Id'] = '<%s>' % to_native(content_id, encoding)
Encoders.encode_base64(self) Encoders.encode_base64(self)
def __init__(self, server=None, sender=None, login=None, tls=True): def __init__(self, server=None, sender=None, login=None, tls=True):
@@ -405,7 +407,7 @@ class Mail(object):
""" """
# We don't want to use base64 encoding for unicode mail # We don't want to use base64 encoding for unicode mail
Charset.add_charset('utf-8', Charset.QP, Charset.QP, 'utf-8') add_charset('utf-8', charset_QP, charset_QP, 'utf-8')
def encode_header(key): def encode_header(key):
if [c for c in key if 32 > ord(c) or ord(c) > 127]: if [c for c in key if 32 > ord(c) or ord(c) > 127]:
@@ -428,12 +430,12 @@ class Mail(object):
if not raw and attachments: if not raw and attachments:
# Use multipart/mixed if there is attachments # Use multipart/mixed if there is attachments
payload_in = MIMEMultipart.MIMEMultipart('mixed') payload_in = MIMEMultipart('mixed')
elif raw: elif raw:
# no encoding configuration for raw messages # no encoding configuration for raw messages
if not isinstance(message, basestring): if not isinstance(message, basestring):
message = message.read() message = message.read()
if isinstance(message, unicode): if isinstance(message, unicodeT):
text = message.encode('utf-8') text = message.encode('utf-8')
elif not encoding == 'utf-8': elif not encoding == 'utf-8':
text = message.decode(encoding).encode('utf-8') text = message.decode(encoding).encode('utf-8')
@@ -442,7 +444,7 @@ class Mail(object):
# No charset passed to avoid transport encoding # No charset passed to avoid transport encoding
# NOTE: some unicode encoded strings will produce # NOTE: some unicode encoded strings will produce
# unreadable mail contents. # unreadable mail contents.
payload_in = MIMEText.MIMEText(text) payload_in = MIMEText(text)
if to: if to:
if not isinstance(to, (list, tuple)): if not isinstance(to, (list, tuple)):
to = [to] to = [to]
@@ -471,14 +473,14 @@ class Mail(object):
if text is not None: if text is not None:
if not isinstance(text, basestring): if not isinstance(text, basestring):
text = text.read() text = text.read()
if isinstance(text, unicode): if isinstance(text, unicodeT):
text = text.encode('utf-8') text = text.encode('utf-8')
elif not encoding == 'utf-8': elif not encoding == 'utf-8':
text = text.decode(encoding).encode('utf-8') text = text.decode(encoding).encode('utf-8')
if html is not None: if html is not None:
if not isinstance(html, basestring): if not isinstance(html, basestring):
html = html.read() html = html.read()
if isinstance(html, unicode): if isinstance(html, unicodeT):
html = html.encode('utf-8') html = html.encode('utf-8')
elif not encoding == 'utf-8': elif not encoding == 'utf-8':
html = html.decode(encoding).encode('utf-8') html = html.decode(encoding).encode('utf-8')
@@ -486,15 +488,13 @@ class Mail(object):
# Construct mime part only if needed # Construct mime part only if needed
if text is not None and html: if text is not None and html:
# We have text and html we need multipart/alternative # We have text and html we need multipart/alternative
attachment = MIMEMultipart.MIMEMultipart('alternative') attachment = MIMEMultipart('alternative')
attachment.attach(MIMEText.MIMEText(text, _charset='utf-8')) attachment.attach(MIMEText(text, _charset='utf-8'))
attachment.attach( attachment.attach(MIMEText(html, 'html', _charset='utf-8'))
MIMEText.MIMEText(html, 'html', _charset='utf-8'))
elif text is not None: elif text is not None:
attachment = MIMEText.MIMEText(text, _charset='utf-8') attachment = MIMEText(text, _charset='utf-8')
elif html: elif html:
attachment = \ attachment = MIMEText(html, 'html', _charset='utf-8')
MIMEText.MIMEText(html, 'html', _charset='utf-8')
if attachments: if attachments:
# If there is attachments put text and html into # If there is attachments put text and html into
@@ -560,12 +560,11 @@ class Mail(object):
c.op_sign(plain, sig, mode.DETACH) c.op_sign(plain, sig, mode.DETACH)
sig.seek(0, 0) sig.seek(0, 0)
# make it part of the email # make it part of the email
payload = \ payload = MIMEMultipart('signed',
MIMEMultipart.MIMEMultipart('signed', boundary=None,
boundary=None, _subparts=None,
_subparts=None, **dict(micalg="pgp-sha1",
**dict(micalg="pgp-sha1", protocol="application/pgp-signature"))
protocol="application/pgp-signature"))
# insert the origin payload # insert the origin payload
payload.attach(payload_in) payload.attach(payload_in)
# insert the detached signature # insert the detached signature
@@ -605,10 +604,10 @@ class Mail(object):
c.op_encrypt(recipients, 1, plain, cipher) c.op_encrypt(recipients, 1, plain, cipher)
cipher.seek(0, 0) cipher.seek(0, 0)
# make it a part of the email # make it a part of the email
payload = MIMEMultipart.MIMEMultipart('encrypted', payload = MIMEMultipart('encrypted',
boundary=None, boundary=None,
_subparts=None, _subparts=None,
**dict(protocol="application/pgp-encrypted")) **dict(protocol="application/pgp-encrypted"))
p = MIMEBase("application", 'pgp-encrypted') p = MIMEBase("application", 'pgp-encrypted')
p.set_payload("Version: 1\r\n") p.set_payload("Version: 1\r\n")
payload.attach(p) payload.attach(p)
@@ -729,29 +728,29 @@ class Mail(object):
payload = payload_in payload = payload_in
if from_address: if from_address:
payload['From'] = encoded_or_raw(from_address.decode(encoding)) payload['From'] = encoded_or_raw(to_unicode(from_address, encoding))
else: else:
payload['From'] = encoded_or_raw(sender.decode(encoding)) payload['From'] = encoded_or_raw(to_unicode(sender, encoding))
origTo = to[:] origTo = to[:]
if to: if to:
payload['To'] = encoded_or_raw(', '.join(to).decode(encoding)) payload['To'] = encoded_or_raw(to_unicode(', '.join(to), encoding))
if reply_to: if reply_to:
payload['Reply-To'] = encoded_or_raw(reply_to.decode(encoding)) payload['Reply-To'] = encoded_or_raw(to_unicode(reply_to, encoding))
if cc: if cc:
payload['Cc'] = encoded_or_raw(', '.join(cc).decode(encoding)) payload['Cc'] = encoded_or_raw(to_unicode(', '.join(cc), encoding))
to.extend(cc) to.extend(cc)
if bcc: if bcc:
to.extend(bcc) to.extend(bcc)
payload['Subject'] = encoded_or_raw(subject.decode(encoding)) payload['Subject'] = encoded_or_raw(to_unicode(subject, encoding))
payload['Date'] = email.utils.formatdate() payload['Date'] = email.utils.formatdate()
for k, v in iteritems(headers): for k, v in iteritems(headers):
payload[k] = encoded_or_raw(v.decode(encoding)) payload[k] = encoded_or_raw(to_unicode(v, encoding))
result = {} result = {}
try: try:
if self.settings.server == 'logging': if self.settings.server == 'logging':
entry = 'email not sent\n%s\nFrom: %s\nTo: %s\nSubject: %s\n\n%s\n%s\n' % \ entry = 'email not sent\n%s\nFrom: %s\nTo: %s\nSubject: %s\n\n%s\n%s\n' % \
('-' * 40, sender, ', '.join(to), subject, text or html, '-' * 40) ('-' * 40, sender, ', '.join(to), subject, text or html, '-' * 40)
logger.warn(entry) logger.warning(entry)
elif self.settings.server.startswith('logging:'): elif self.settings.server.startswith('logging:'):
entry = 'email not sent\n%s\nFrom: %s\nTo: %s\nSubject: %s\n\n%s\n%s\n' % \ entry = 'email not sent\n%s\nFrom: %s\nTo: %s\nSubject: %s\n\n%s\n%s\n' % \
('-' * 40, sender, ', '.join(to), subject, text or html, '-' * 40) ('-' * 40, sender, ', '.join(to), subject, text or html, '-' * 40)
@@ -773,16 +772,16 @@ class Mail(object):
if attachments: if attachments:
result = mail.send_mail( result = mail.send_mail(
sender=sender, to=origTo, sender=sender, to=origTo,
subject=unicode(subject, encoding), body=unicode(text, encoding), html=html, subject=to_unicode(subject, encoding), body=to_unicode(text, encoding), html=html,
attachments=attachments, **xcc) attachments=attachments, **xcc)
elif html and (not raw): elif html and (not raw):
result = mail.send_mail( result = mail.send_mail(
sender=sender, to=origTo, sender=sender, to=origTo,
subject=unicode(subject, encoding), body=unicode(text, encoding), html=html, **xcc) subject=to_unicode(subject, encoding), body=to_unicode(text, encoding), html=html, **xcc)
else: else:
result = mail.send_mail( result = mail.send_mail(
sender=sender, to=origTo, sender=sender, to=origTo,
subject=unicode(subject, encoding), body=unicode(text, encoding), **xcc) subject=to_unicode(subject, encoding), body=to_unicode(text, encoding), **xcc)
else: else:
smtp_args = self.settings.server.split(':') smtp_args = self.settings.server.split(':')
kwargs = dict(timeout=self.settings.timeout) kwargs = dict(timeout=self.settings.timeout)
@@ -800,7 +799,7 @@ class Mail(object):
sender, to, payload.as_string()) sender, to, payload.as_string())
server.quit() server.quit()
except Exception as e: except Exception as e:
logger.warn('Mail.send failure:%s' % e) logger.warning('Mail.send failure:%s' % e)
self.result = result self.result = result
self.error = e self.error = e
return False return False
@@ -1250,8 +1249,7 @@ class AuthJWT(object):
@staticmethod @staticmethod
def jwt_b64e(string): def jwt_b64e(string):
if isinstance(string, unicode): string = to_bytes(string)
string = string.encode('utf-8', 'strict')
return base64.urlsafe_b64encode(string).strip(b'=') return base64.urlsafe_b64encode(string).strip(b'=')
@staticmethod @staticmethod
@@ -1260,47 +1258,44 @@ class AuthJWT(object):
called with a unicode string). called with a unicode string).
The result is also a bytestring. The result is also a bytestring.
""" """
if isinstance(string, unicode): string = to_bytes(string, 'ascii', 'ignore')
string = string.encode('ascii', 'ignore') return base64.urlsafe_b64decode(string + b'=' * (-len(string) % 4))
return base64.urlsafe_b64decode(string + '=' * (-len(string) % 4))
def generate_token(self, payload): def generate_token(self, payload):
secret = self.secret_key secret = to_bytes(self.secret_key)
if self.salt: if self.salt:
if callable(self.salt): if callable(self.salt):
secret = "%s$%s" % (secret, self.salt(payload)) secret = "%s$%s" % (secret, self.salt(payload))
else: else:
secret = "%s$%s" % (secret, self.salt) secret = "%s$%s" % (secret, self.salt)
if isinstance(secret, unicode): if isinstance(secret, unicodeT):
secret = secret.encode('ascii', 'ignore') secret = secret.encode('ascii', 'ignore')
b64h = self.cached_b64h b64h = self.cached_b64h
b64p = self.jwt_b64e(serializers.json(payload)) b64p = self.jwt_b64e(serializers.json(payload))
jbody = b64h + '.' + b64p jbody = b64h + b'.' + b64p
mauth = hmac.new(key=secret, msg=jbody, digestmod=self.digestmod) mauth = hmac.new(key=secret, msg=jbody, digestmod=self.digestmod)
jsign = self.jwt_b64e(mauth.digest()) jsign = self.jwt_b64e(mauth.digest())
return jbody + '.' + jsign return to_native(jbody + b'.' + jsign)
def verify_signature(self, body, signature, secret): def verify_signature(self, body, signature, secret):
mauth = hmac.new(key=secret, msg=body, digestmod=self.digestmod) mauth = hmac.new(key=secret, msg=body, digestmod=self.digestmod)
return compare(self.jwt_b64e(mauth.digest()), signature) return compare(self.jwt_b64e(mauth.digest()), signature)
def load_token(self, token): def load_token(self, token):
if isinstance(token, unicode): token = to_bytes(token, 'utf-8', 'strict')
token = token.encode('utf-8', 'strict') body, sig = token.rsplit(b'.', 1)
body, sig = token.rsplit('.', 1) b64h, b64b = body.split(b'.', 1)
b64h, b64b = body.split('.', 1)
if b64h != self.cached_b64h: if b64h != self.cached_b64h:
# header not the same # header not the same
raise HTTP(400, u'Invalid JWT Header') raise HTTP(400, u'Invalid JWT Header')
secret = self.secret_key secret = self.secret_key
tokend = serializers.loads_json(self.jwt_b64d(b64b)) tokend = serializers.loads_json(to_native(self.jwt_b64d(b64b)))
if self.salt: if self.salt:
if callable(self.salt): if callable(self.salt):
secret = "%s$%s" % (secret, self.salt(tokend)) secret = "%s$%s" % (secret, self.salt(tokend))
else: else:
secret = "%s$%s" % (secret, self.salt) secret = "%s$%s" % (secret, self.salt)
if isinstance(secret, unicode): secret = to_bytes(secret, 'ascii', 'ignore')
secret = secret.encode('ascii', 'ignore')
if not self.verify_signature(body, sig, secret): if not self.verify_signature(body, sig, secret):
# signature verification failed # signature verification failed
raise HTTP(400, u'Token signature is invalid') raise HTTP(400, u'Token signature is invalid')
@@ -1462,7 +1457,7 @@ class AuthJWT(object):
def f(*args, **kwargs): def f(*args, **kwargs):
try: try:
token = self.get_jwt_token_from_request(token_param=token_param) token = self.get_jwt_token_from_request(token_param=token_param)
except HTTP, e: except HTTP as e:
if required: if required:
raise e raise e
token = None token = None
@@ -3560,8 +3555,8 @@ class Auth(object):
password = '' password = ''
specials = r'!#$*' specials = r'!#$*'
for i in range(0, 3): for i in range(0, 3):
password += random.choice(string.lowercase) password += random.choice(string.ascii_lowercase)
password += random.choice(string.uppercase) password += random.choice(string.ascii_uppercase)
password += random.choice(string.digits) password += random.choice(string.digits)
password += random.choice(specials) password += random.choice(specials)
return ''.join(random.sample(password, len(password))) return ''.join(random.sample(password, len(password)))
@@ -3994,7 +3989,7 @@ class Auth(object):
requires = table_user[passfield].requires requires = table_user[passfield].requires
if not isinstance(requires, (list, tuple)): if not isinstance(requires, (list, tuple)):
requires = [requires] requires = [requires]
requires = filter(lambda t: isinstance(t, CRYPT), requires) requires = list(filter(lambda t: isinstance(t, CRYPT), requires))
if requires: if requires:
requires[0].min_length = 0 requires[0].min_length = 0
form = SQLFORM.factory( form = SQLFORM.factory(
@@ -4764,7 +4759,7 @@ class Auth(object):
self._wiki.automenu() self._wiki.automenu()
class Crud(object): class Crud(object): # pragma: no cover
def url(self, f=None, args=None, vars=None): def url(self, f=None, args=None, vars=None):
""" """
@@ -5621,7 +5616,7 @@ class Service(object):
args = request.args args = request.args
def none_exception(value): def none_exception(value):
if isinstance(value, unicode): if isinstance(value, unicodeT):
return value.encode('utf8') return value.encode('utf8')
if hasattr(value, 'isoformat'): if hasattr(value, 'isoformat'):
return value.isoformat()[:19].replace('T', ' ') return value.isoformat()[:19].replace('T', ' ')
+13 -13
View File
@@ -11,7 +11,7 @@ Utilities and class for UTF8 strings managing
---------------------------------------------- ----------------------------------------------
""" """
from __future__ import print_function from __future__ import print_function
from gluon._compat import builtin as __builtin__, unicodeT, iteritems, to_unicode from gluon._compat import builtin as __builtin__, unicodeT, iteritems, to_unicode, to_native
__all__ = ['Utf8'] __all__ = ['Utf8']
@@ -51,10 +51,10 @@ def sort_key(s):
from gluon.contrib.pyuca import unicode_collator from gluon.contrib.pyuca import unicode_collator
unicode_sort_key = unicode_collator.sort_key unicode_sort_key = unicode_collator.sort_key
sort_key = lambda s: unicode_sort_key( sort_key = lambda s: unicode_sort_key(
unicode(s, 'utf-8') if isinstance(s, str) else s) to_unicode(s, 'utf-8') if isinstance(s, str) else s)
except: except:
sort_key = lambda s: ( sort_key = lambda s: (
unicode(s, 'utf-8') if isinstance(s, str) else s).lower() to_unicode(s, 'utf-8') if isinstance(s, str) else s).lower()
return sort_key(s) return sort_key(s)
@@ -64,7 +64,7 @@ def ord(char):
""" """
if isinstance(char, unicodeT): if isinstance(char, unicodeT):
return __builtin__.ord(char) return __builtin__.ord(char)
return __builtin__.ord(unicode(char, 'utf-8')) return __builtin__.ord(to_unicode(char, 'utf-8'))
def chr(code): def chr(code):
@@ -92,8 +92,8 @@ def truncate(string, length, dots='...'):
Returns: Returns:
(utf8-str): original or cutted string (utf8-str): original or cutted string
""" """
text = unicode(string, 'utf-8') text = to_unicode(string, 'utf-8')
dots = unicode(dots, 'utf-8') if isinstance(dots, str) else dots dots = to_unicode(dots, 'utf-8') if isinstance(dots, str) else dots
if len(text) > length: if len(text) > length:
text = text[:length - len(dots)] + dots text = text[:length - len(dots)] + dots
return str.__new__(Utf8, text.encode('utf-8')) return str.__new__(Utf8, text.encode('utf-8'))
@@ -120,11 +120,11 @@ class Utf8(str):
""" """
def __new__(cls, content='', codepage='utf-8'): def __new__(cls, content='', codepage='utf-8'):
if isinstance(content, unicodeT): if isinstance(content, unicodeT):
return str.__new__(cls, unicode.encode(content, 'utf-8')) return str.__new__(cls, to_native(content, 'utf-8'))
elif codepage in ('utf-8', 'utf8') or isinstance(content, cls): elif codepage in ('utf-8', 'utf8') or isinstance(content, cls):
return str.__new__(cls, content) return str.__new__(cls, content)
else: else:
return str.__new__(cls, unicode(content, codepage).encode('utf-8')) return str.__new__(cls, to_native(to_unicode(content, codepage), 'utf-8'))
def __repr__(self): def __repr__(self):
r''' # note that we use raw strings to avoid having to use double back slashes below r''' # note that we use raw strings to avoid having to use double back slashes below
@@ -156,9 +156,9 @@ class Utf8(str):
True True
''' '''
if str.find(self, "'") >= 0 and str.find(self, '"') < 0: # only single quote exists if str.find(self, "'") >= 0 and str.find(self, '"') < 0: # only single quote exists
return '"' + unicode(self, 'utf-8').translate(repr_escape_tab).encode('utf-8') + '"' return '"' + to_native(to_unicode(self, 'utf-8').translate(repr_escape_tab), 'utf-8') + '"'
else: else:
return "'" + unicode(self, 'utf-8').translate(repr_escape_tab2).encode('utf-8') + "'" return "'" + to_native(to_unicode(self, 'utf-8').translate(repr_escape_tab2), 'utf-8') + "'"
def __size__(self): def __size__(self):
""" length of utf-8 string in bytes """ """ length of utf-8 string in bytes """
@@ -168,17 +168,17 @@ class Utf8(str):
return str.__contains__(self, Utf8(other)) return str.__contains__(self, Utf8(other))
def __getitem__(self, index): def __getitem__(self, index):
return str.__new__(Utf8, unicode(self, 'utf-8')[index].encode('utf-8')) return str.__new__(Utf8, to_native(to_unicode(self, 'utf-8')[index], 'utf-8'))
def __getslice__(self, begin, end): def __getslice__(self, begin, end):
return str.__new__(Utf8, unicode(self, 'utf-8')[begin:end].encode('utf-8')) return str.__new__(Utf8, to_native(to_unicode(self, 'utf-8')[begin:end], 'utf-8'))
def __add__(self, other): def __add__(self, other):
return str.__new__(Utf8, str.__add__(self, unicode.encode(other, 'utf-8') return str.__new__(Utf8, str.__add__(self, unicode.encode(other, 'utf-8')
if isinstance(other, unicode) else other)) if isinstance(other, unicode) else other))
def __len__(self): def __len__(self):
return len(unicode(self, 'utf-8')) return len(to_unicode(self, 'utf-8'))
def __mul__(self, integer): def __mul__(self, integer):
return str.__new__(Utf8, str.__mul__(self, integer)) return str.__new__(Utf8, str.__mul__(self, integer))
+10 -6
View File
@@ -85,7 +85,7 @@ def compare(a, b):
def md5_hash(text): def md5_hash(text):
""" Generates a md5 hash with the given text """ """ Generates a md5 hash with the given text """
return md5(text).hexdigest() return md5(to_bytes(text)).hexdigest()
def simple_hash(text, key='', salt='', digest_alg='md5'): def simple_hash(text, key='', salt='', digest_alg='md5'):
@@ -157,11 +157,12 @@ def get_callable_argspec(fn):
return inspect.getargspec(inspectable) return inspect.getargspec(inspectable)
def pad(s, n=32, padchar=' '): def pad(s, n=32, padchar=b' '):
return s + (32 - len(s) % 32) * padchar return s + (32 - len(s) % 32) * padchar
def secure_dumps(data, encryption_key, hash_key=None, compression_level=None): def secure_dumps(data, encryption_key, hash_key=None, compression_level=None):
encryption_key = to_bytes(encryption_key)
if not hash_key: if not hash_key:
hash_key = sha1(encryption_key).hexdigest() hash_key = sha1(encryption_key).hexdigest()
dump = pickle.dumps(data, pickle.HIGHEST_PROTOCOL) dump = pickle.dumps(data, pickle.HIGHEST_PROTOCOL)
@@ -170,17 +171,20 @@ def secure_dumps(data, encryption_key, hash_key=None, compression_level=None):
key = pad(encryption_key)[:32] key = pad(encryption_key)[:32]
cipher, IV = AES_new(key) cipher, IV = AES_new(key)
encrypted_data = base64.urlsafe_b64encode(IV + cipher.encrypt(pad(dump))) encrypted_data = base64.urlsafe_b64encode(IV + cipher.encrypt(pad(dump)))
signature = hmac.new(hash_key, encrypted_data).hexdigest() signature = to_bytes(hmac.new(to_bytes(hash_key), encrypted_data).hexdigest())
return signature + ':' + encrypted_data return signature + b':' + encrypted_data
def secure_loads(data, encryption_key, hash_key=None, compression_level=None): def secure_loads(data, encryption_key, hash_key=None, compression_level=None):
encryption_key = to_bytes(encryption_key)
data = to_native(data)
if ':' not in data: if ':' not in data:
return None return None
if not hash_key: if not hash_key:
hash_key = sha1(encryption_key).hexdigest() hash_key = sha1(encryption_key).hexdigest()
signature, encrypted_data = data.split(':', 1) signature, encrypted_data = data.split(':', 1)
actual_signature = hmac.new(hash_key, encrypted_data).hexdigest() encrypted_data = to_bytes(encrypted_data)
actual_signature = hmac.new(to_bytes(hash_key), encrypted_data).hexdigest()
if not compare(signature, actual_signature): if not compare(signature, actual_signature):
return None return None
key = pad(encryption_key)[:32] key = pad(encryption_key)[:32]
@@ -189,7 +193,7 @@ def secure_loads(data, encryption_key, hash_key=None, compression_level=None):
cipher, _ = AES_new(key, IV=IV) cipher, _ = AES_new(key, IV=IV)
try: try:
data = cipher.decrypt(encrypted_data) data = cipher.decrypt(encrypted_data)
data = data.rstrip(' ') data = data.rstrip(b' ')
if compression_level: if compression_level:
data = zlib.decompress(data) data = zlib.decompress(data)
return pickle.loads(data) return pickle.loads(data)