diff --git a/.gitignore b/.gitignore
index 95b4e69b..92f69758 100644
--- a/.gitignore
+++ b/.gitignore
@@ -48,6 +48,7 @@ applications/*/sessions/*
applications/*/errors/*
applications/*/cache/*
applications/*/uploads/*
+applications/*/private/*
applications/*/*.py[oc]
applications/*/static/temp
applications/*/progress.log
diff --git a/applications/welcome/controllers/appadmin.py b/applications/welcome/controllers/appadmin.py
index 2f780749..ddb02548 100644
--- a/applications/welcome/controllers/appadmin.py
+++ b/applications/welcome/controllers/appadmin.py
@@ -267,7 +267,7 @@ def select():
else:
rows = db(query, ignore_common_filters=True).select(
*fields, limitby=(start, stop))
- except Exception, e:
+ except Exception as e:
import traceback
tb = traceback.format_exc()
(rows, nrows) = ([], 0)
@@ -286,7 +286,7 @@ def select():
import_csv(db[request.vars.table],
request.vars.csvfile.file)
response.flash = T('data uploaded')
- except Exception, e:
+ except Exception as e:
response.flash = DIV(T('unable to parse csv file'), PRE(str(e)))
# end handle upload csv
diff --git a/gluon/_compat.py b/gluon/_compat.py
index 982a78f9..c6c36120 100644
--- a/gluon/_compat.py
+++ b/gluon/_compat.py
@@ -17,6 +17,13 @@ if PY2:
import thread
import Cookie
import urllib2
+ import Queue
+ import ConfigParser as configparser
+ from email.MIMEBase import MIMEBase
+ from email.Header import Header
+ from email import MIMEMultipart, MIMEText, Encoders, Charset
+ from urllib import FancyURLopener
+ from urllib import quote as urllib_quote, unquote as urllib_unquote
reduce = reduce
hashlib_md5 = hashlib.md5
iterkeys = lambda d: d.iterkeys()
@@ -67,6 +74,16 @@ else:
from html.entities import entitydefs, name2codepoint
import builtins as builtin
import _thread as thread
+ import configparser
+ import queue as Queue
+ from email.mime.base import MIMEBase
+ from email.mime.multipart import MIMEMultipart
+ from email.mime.text import MIMEText
+ from email import encoders as Encoders
+ from email.header import Header
+ from email.charset import Charset
+ from urllib.request import FancyURLopener
+ from urllib.parse import quote as urllib_quote, unquote as urllib_unquote
hashlib_md5 = lambda s: hashlib.md5(bytes(s, 'utf8'))
iterkeys = lambda d: iter(d.keys())
itervalues = lambda d: iter(d.values())
diff --git a/gluon/cache.py b/gluon/cache.py
index 4990362e..814b5bc1 100644
--- a/gluon/cache.py
+++ b/gluon/cache.py
@@ -155,7 +155,7 @@ class CacheAbstract(object):
Auxiliary function called by `clear` to search and clear cache entries
"""
r = re.compile(regex)
- for key in storage.keys():
+ for key in list(storage.keys()):
if r.match(str(key)):
del storage[key]
return
diff --git a/gluon/contrib/autolinks.py b/gluon/contrib/autolinks.py
index 1bd2bc8e..4670c304 100644
--- a/gluon/contrib/autolinks.py
+++ b/gluon/contrib/autolinks.py
@@ -42,6 +42,7 @@ revision3.com
viddler.com
"""
from __future__ import print_function
+from gluon._compat import FancyURLopener, urllib_quote
import re
import cgi
@@ -94,7 +95,7 @@ def video(url):
def googledoc_viewer(url):
- return '' % urllib.quote(url)
+ return '' % urllib_quote(url)
def web2py_component(url):
@@ -136,7 +137,7 @@ EXTENSION_MAPS = {
}
-class VimeoURLOpener(urllib.FancyURLopener):
+class VimeoURLOpener(FancyURLopener):
"Vimeo blocks the urllib user agent for some reason"
version = "Mozilla/4.0"
urllib._urlopener = VimeoURLOpener()
diff --git a/gluon/contrib/markmin/markmin2html.py b/gluon/contrib/markmin/markmin2html.py
index c053a6b0..431f0cef 100755
--- a/gluon/contrib/markmin/markmin2html.py
+++ b/gluon/contrib/markmin/markmin2html.py
@@ -7,7 +7,7 @@ from __future__ import print_function
import re
import urllib
from cgi import escape
-from string import maketrans
+from gluon._compat import maketrans, urllib_quote
try:
from ast import parse as ast_parse
@@ -1418,7 +1418,7 @@ def render(text,
return '[' + ','.join('%s' %
(id_prefix + d, b, d) for d in escape(code).split(',')) + ']'
elif b == 'latex':
- return LATEX % urllib.quote(code)
+ return LATEX % urllib_quote(code)
elif b in html_colors:
return '%s' \
% (b, render(code, {}, {}, 'br', URL, environment, latex,
diff --git a/gluon/globals.py b/gluon/globals.py
index cb27b37a..df4f5c68 100644
--- a/gluon/globals.py
+++ b/gluon/globals.py
@@ -13,7 +13,7 @@ Contains the classes for the global used variables:
- Session
"""
-from gluon._compat import pickle, StringIO, copyreg, Cookie, urlparse, PY2
+from gluon._compat import pickle, StringIO, copyreg, Cookie, urlparse, PY2, iteritems
from gluon.storage import Storage, List
from gluon.streamer import streamer, stream_file_or_304_or_206, DEFAULT_CHUNK_SIZE
from gluon.contenttype import contenttype
@@ -198,7 +198,7 @@ class Request(Storage):
query_string = self.env.get('query_string', '')
dget = urlparse.parse_qs(query_string, keep_blank_values=1) # Ref: https://docs.python.org/2/library/cgi.html#cgi.parse_qs
get_vars = self._get_vars = Storage(dget)
- for (key, value) in get_vars.iteritems():
+ for (key, value) in iteritems(get_vars):
if isinstance(value, list) and len(value) == 1:
get_vars[key] = value[0]
@@ -272,7 +272,7 @@ class Request(Storage):
"""Merges get_vars and post_vars to vars
"""
self._vars = copy.copy(self.get_vars)
- for key, value in self.post_vars.iteritems():
+ for key, value in iteritems(self.post_vars):
if key not in self._vars:
self._vars[key] = value
else:
@@ -448,7 +448,7 @@ class Response(Storage):
def include_meta(self):
s = "\n"
- for meta in (self.meta or {}).iteritems():
+ for meta in iteritems((self.meta or {})):
k, v = meta
if isinstance(v, dict):
s += '\n'
@@ -680,7 +680,7 @@ class Response(Storage):
dbstats = []
dbtables = {}
infos = DAL.get_instances()
- for k, v in infos.iteritems():
+ for k, v in iteritems(infos):
dbstats.append(TABLE(*[TR(PRE(row[0]), '%.2fms' % (row[1]*1000))
for row in v['dbstats']]))
dbtables[k] = dict(defined=v['dbtables']['defined'] or '[no defined tables]',
diff --git a/gluon/html.py b/gluon/html.py
index 31a1b505..96946048 100644
--- a/gluon/html.py
+++ b/gluon/html.py
@@ -20,7 +20,7 @@ import urllib
import base64
from gluon import sanitizer, decoder
import itertools
-from gluon._compat import reduce, pickle, copyreg, HTMLParser, name2codepoint, iteritems, unichr, unicodeT
+from gluon._compat import reduce, pickle, copyreg, HTMLParser, name2codepoint, iteritems, unichr, unicodeT, urllib_quote
import marshal
from gluon.storage import Storage
@@ -252,7 +252,7 @@ def URL(a=None,
'/a/c/f#%25%28id%29d'
"""
- from rewrite import url_out # done here in case used not-in web2py
+ from gluon.rewrite import url_out # done here in case used not-in web2py
if args in (None, []):
args = []
@@ -269,7 +269,7 @@ def URL(a=None,
(f, a, c) = (a, c, f)
elif a and c and not f:
(c, f, a) = (a, c, f)
- from globals import current
+ from gluon.globals import current
if hasattr(current, 'request'):
r = current.request
@@ -304,7 +304,7 @@ def URL(a=None,
if controller == 'static':
extension = None
# add static version to url
- from globals import current
+ from gluon.globals import current
if hasattr(current, 'response'):
response = current.response
if response.static_version and response.static_version_urls:
@@ -322,9 +322,9 @@ def URL(a=None,
if args:
if url_encode:
if encode_embedded_slash:
- other = '/' + '/'.join([urllib.quote(str(x), '') for x in args])
+ other = '/' + '/'.join([urllib_quote(str(x), '') for x in args])
else:
- other = args and urllib.quote('/' + '/'.join([str(x) for x in args]))
+ other = args and urllib_quote('/' + '/'.join([str(x) for x in args]))
else:
other = args and ('/' + '/'.join([str(x) for x in args]))
else:
@@ -377,7 +377,7 @@ def URL(a=None,
other += '?%s' % '&'.join(['%s=%s' % var[:2] for var in list_vars])
if anchor:
if url_encode:
- other += '#' + urllib.quote(str(anchor))
+ other += '#' + urllib_quote(str(anchor))
else:
other += '#' + (str(anchor))
if extension:
@@ -451,7 +451,7 @@ def verifyURL(request, hmac_key=None, hash_vars=True, salt=None, user_signature=
# join all the args & vars into one long string
# always include all of the args
- other = args and urllib.quote('/' + '/'.join([str(x) for x in args])) or ''
+ other = args and urllib_quote('/' + '/'.join([str(x) for x in args])) or ''
h_args = '/%s/%s/%s.%s%s' % (request.application,
request.controller,
request.function,
@@ -723,7 +723,7 @@ class DIV(XmlComponent):
dictionary like updating of the tag attributes
"""
- for (key, value) in kargs.iteritems():
+ for (key, value) in iteritems(kargs):
self[key] = value
return self
@@ -924,7 +924,7 @@ class DIV(XmlComponent):
# get the attributes for this component
# (they start with '_', others may have special meanings)
attr = []
- for key, value in self.attributes.iteritems():
+ for key, value in iteritems(self.attributes):
if key[:1] != '_':
continue
name = key[1:]
@@ -934,7 +934,7 @@ class DIV(XmlComponent):
continue
attr.append((name, value))
data = self.attributes.get('data', {})
- for key, value in data.iteritems():
+ for key, value in iteritems(data):
name = 'data-' + key
value = data[key]
attr.append((name, value))
@@ -1127,7 +1127,7 @@ class DIV(XmlComponent):
# value as provided
tag = getattr(self, 'tag').replace('/', '')
check = not (args and tag not in args)
- for (key, value) in kargs.iteritems():
+ for (key, value) in iteritems(kargs):
if key not in ['first_only', 'replace', 'find_text']:
if isinstance(value, (str, int)):
if str(self[key]) != str(value):
@@ -1211,7 +1211,7 @@ class DIV(XmlComponent):
tag = getattr(c, 'tag').replace("/", "")
if args and tag not in args:
check = False
- for (key, value) in kargs.iteritems():
+ for (key, value) in iteritems(kargs):
if c[key] != value:
check = False
if check:
@@ -2158,7 +2158,7 @@ class FORM(DIV):
c = []
attr = self.attributes.get('hidden', {})
if 'hidden' in self.attributes:
- c = [INPUT(_type='hidden', _name=key, _value=value) for (key, value) in attr.iteritems()]
+ c = [INPUT(_type='hidden', _name=key, _value=value) for (key, value) in iteritems(attr)]
if hasattr(self, 'formkey') and self.formkey:
c.append(INPUT(_type='hidden', _name='_formkey', _value=self.formkey))
if hasattr(self, 'formname') and self.formname:
@@ -2235,9 +2235,9 @@ class FORM(DIV):
onsuccess(self)
if next:
if self.vars:
- for key, value in self.vars.iteritems():
+ for key, value in iteritems(self.vars):
next = next.replace('[%s]' % key,
- urllib.quote(str(value)))
+ urllib_quote(str(value)))
if not next.startswith('/'):
next = URL(next)
redirect(next)
@@ -2309,11 +2309,11 @@ class FORM(DIV):
inputs = [INPUT(_type='button',
_value=name,
_onclick=FORM.REDIRECT_JS % link)
- for name, link in buttons.iteritems()]
+ for name, link in iteritems(buttons)]
inputs += [INPUT(_type='hidden',
_name=name,
_value=value)
- for name, value in hidden.iteritems()]
+ for name, value in iteritems(hidden)]
form = FORM(INPUT(_type='submit', _value=text), *inputs)
form.process()
return form
diff --git a/gluon/languages.py b/gluon/languages.py
index 6e434c83..3a303850 100644
--- a/gluon/languages.py
+++ b/gluon/languages.py
@@ -18,7 +18,7 @@ import pkgutil
import logging
from cgi import escape
from threading import RLock
-from gluon._compat import copyreg, PY2, maketrans
+from gluon._compat import copyreg, PY2, maketrans, iterkeys, unicodeT, to_unicode
from gluon.portalocker import read_locked, LockedFile
from gluon.utf8 import Utf8
@@ -334,7 +334,7 @@ def write_dict(filename, contents):
try:
fp = LockedFile(filename, 'w')
fp.write('# -*- coding: utf-8 -*-\n{\n')
- for key in sorted(contents, sort_function):
+ for key in sorted(contents, key=lambda x: to_unicode(x, 'utf-8').lower()):
fp.write('%s: %s,\n' % (repr(Utf8(key)),
repr(Utf8(contents[key]))))
fp.write('}\n')
@@ -541,7 +541,7 @@ class translator(object):
def get_possible_languages(self):
""" Gets list of all possible languages for current application """
return list(set(self.current_languages +
- [lang for lang in read_possible_languages(self.langpath).iterkeys()
+ [lang for lang in iterkeys(read_possible_languages(self.langpath))
if lang != 'default']))
def set_current_languages(self, *languages):
@@ -658,7 +658,7 @@ class translator(object):
languages = []
self.requested_languages = languages = tuple(languages)
if languages:
- all_languages = set(lang for lang in pl_info.iterkeys()
+ all_languages = set(lang for lang in iterkeys(pl_info)
if lang != 'default') \
| set(self.current_languages)
for lang in languages:
@@ -800,9 +800,9 @@ class translator(object):
the ## notation is ignored in multiline strings and strings that
start with ##. This is needed to allow markmin syntax to be translated
"""
- if isinstance(message, unicode):
+ if isinstance(message, unicodeT):
message = message.encode('utf8')
- if isinstance(prefix, unicode):
+ if isinstance(prefix, unicodeT):
prefix = prefix.encode('utf8')
key = prefix + message
mt = self.t.get(key, None)
diff --git a/gluon/recfile.py b/gluon/recfile.py
index d5d772e5..51f81cf9 100755
--- a/gluon/recfile.py
+++ b/gluon/recfile.py
@@ -10,7 +10,7 @@ Generates names for cache and session files
--------------------------------------------
"""
import os
-
+from gluon._compat import builtin
def generate(filename, depth=2, base=512):
if os.path.sep in filename:
@@ -62,4 +62,4 @@ def open(filename, mode="r", path=None):
fullfilename = os.path.join(path, generate(filename))
if mode.startswith('w') and not os.path.exists(os.path.dirname(fullfilename)):
os.makedirs(os.path.dirname(fullfilename))
- return file(fullfilename, mode)
+ return builtin.open(fullfilename, mode)
diff --git a/gluon/rewrite.py b/gluon/rewrite.py
index 3ada7b7c..0bb938b1 100644
--- a/gluon/rewrite.py
+++ b/gluon/rewrite.py
@@ -27,6 +27,7 @@ from gluon.storage import Storage, List
from gluon.http import HTTP
from gluon.fileutils import abspath, read_file
from gluon.settings import global_settings
+from gluon._compat import urllib_unquote, urllib_quote
isdir = os.path.isdir
isfile = os.path.isfile
@@ -625,7 +626,7 @@ def regex_url_in(request, environ):
# serve if a static file
# ##################################################
- path = urllib.unquote(request.env.path_info) or '/'
+ path = urllib_unquote(request.env.path_info) or '/'
path = path.replace('\\', '/')
if path.endswith('/') and len(path) > 1:
path = path[:-1]
@@ -715,7 +716,7 @@ def filter_url(url, method='get', remote='0.0.0.0',
if isinstance(domain, str):
domain = (domain, None)
(path_info, query_string) = (uri[:k], uri[k + 1:])
- path_info = urllib.unquote(path_info) # simulate server
+ path_info = urllib_unquote(path_info) # simulate server
e = {
'REMOTE_ADDR': remote,
'REQUEST_METHOD': method,
@@ -1100,7 +1101,7 @@ class MapUrlIn(object):
uri = '/%s%s%s%s' % (
app,
uri,
- urllib.quote('/' + '/'.join(
+ urllib_quote('/' + '/'.join(
str(x) for x in self.args)) if self.args else '',
('?' + self.query) if self.query else '')
self.env['REQUEST_URI'] = uri
diff --git a/gluon/scheduler.py b/gluon/scheduler.py
index 27721c99..24949946 100644
--- a/gluon/scheduler.py
+++ b/gluon/scheduler.py
@@ -9,7 +9,7 @@ Background processes made simple
---------------------------------
"""
from __future__ import print_function
-
+from gluon._compat import Queue
import os
import time
@@ -24,7 +24,7 @@ import logging
import optparse
import tempfile
import types
-import Queue
+
from json import loads, dumps
from gluon import DAL, Field, IS_NOT_EMPTY, IS_IN_SET, IS_NOT_IN_DB
from gluon import IS_INT_IN_RANGE, IS_DATETIME, IS_IN_DB
diff --git a/gluon/sqlhtml.py b/gluon/sqlhtml.py
index e8404dda..b396f939 100644
--- a/gluon/sqlhtml.py
+++ b/gluon/sqlhtml.py
@@ -19,7 +19,7 @@ import urllib
import re
import os
-from gluon._compat import StringIO, unichr
+from gluon._compat import StringIO, unichr, urllib_quote
from gluon.http import HTTP, redirect
from gluon.html import XmlComponent, truncate_string
from gluon.html import XML, SPAN, TAG, A, DIV, CAT, UL, LI, TEXTAREA, BR, IMG
@@ -1329,10 +1329,10 @@ class SQLFORM(FORM):
db = linkto.split('/')[-1]
for rfld in table._referenced_by:
if keyed:
- query = urllib.quote('%s.%s==%s' % (
+ query = urllib_quote('%s.%s==%s' % (
db, rfld, record[rfld.type[10:].split('.')[1]]))
else:
- query = urllib.quote(
+ query = urllib_quote(
'%s.%s==%s' % (db, rfld, record[self.id_field_name]))
lname = olname = '%s.%s' % (rfld.tablename, rfld.name)
if ofields and olname not in ofields:
diff --git a/gluon/storage.py b/gluon/storage.py
index 80cbffd1..2d8c3dfa 100644
--- a/gluon/storage.py
+++ b/gluon/storage.py
@@ -307,7 +307,7 @@ class List(list):
if not value and otherwise:
raise ValueError('Otherwise will raised.')
except (ValueError, TypeError):
- from http import HTTP, redirect
+ from gluon.http import HTTP, redirect
if otherwise is None:
raise HTTP(404)
elif isinstance(otherwise, str):
diff --git a/gluon/template.py b/gluon/template.py
index 0e297e92..9782eed1 100644
--- a/gluon/template.py
+++ b/gluon/template.py
@@ -18,10 +18,7 @@ import os
import cgi
import logging
from re import compile, sub, escape, DOTALL
-try:
- import cStringIO as StringIO
-except:
- from io import StringIO
+from gluon._compat import StringIO
try:
# have web2py
@@ -279,13 +276,12 @@ class TemplateParser(object):
self.context = context
# allow optional alternative delimiters
-
if delimiters != self.default_delimiters:
escaped_delimiters = (escape(delimiters[0]),
escape(delimiters[1]))
self.r_tag = compile(r'(%s.*?%s)' % escaped_delimiters, DOTALL)
elif hasattr(context.get('response', None), 'delimiters'):
- if context['response'].delimiters != self.default_delimiters:
+ if (context['response'].delimiters != self.default_delimiters) and (context['response'].delimiters != None):
delimiters = context['response'].delimiters
escaped_delimiters = (
escape(delimiters[0]),
@@ -807,7 +803,7 @@ def get_parsed(text):
class DummyResponse():
def __init__(self):
- self.body = StringIO.StringIO()
+ self.body = StringIO()
def write(self, data, escape=True):
if not escape:
@@ -904,7 +900,7 @@ def render(content="hello world",
# save current response class
if context and 'response' in context:
old_response_body = context['response'].body
- context['response'].body = StringIO.StringIO()
+ context['response'].body = StringIO()
else:
old_response_body = None
context['response'] = Response()
@@ -921,7 +917,7 @@ def render(content="hello world",
stream = open(filename, 'rb')
close_stream = True
elif content:
- stream = StringIO.StringIO(content)
+ stream = StringIO(content)
# Execute the template.
code = str(TemplateParser(stream.read(
diff --git a/gluon/tests/__init__.py b/gluon/tests/__init__.py
index 449cfa6f..b5c32d0a 100644
--- a/gluon/tests/__init__.py
+++ b/gluon/tests/__init__.py
@@ -17,12 +17,12 @@ from .test_serializers import *
from .test_template import *
from .test_validators import *
from .test_utils import *
-from .test_contribs import *
-from .test_web import *
from .test_dal import *
from .test_tools import *
from .test_appadmin import *
from .test_scheduler import *
if sys.version[:3] == '2.7':
- from test_old_doctests import *
+ from .test_web import *
+ from .test_contribs import *
+ from .test_old_doctests import *
diff --git a/gluon/tests/test_appadmin.py b/gluon/tests/test_appadmin.py
index f8a62ee6..c47ac54f 100644
--- a/gluon/tests/test_appadmin.py
+++ b/gluon/tests/test_appadmin.py
@@ -10,15 +10,15 @@ import sys
import unittest
import json
-from fix_path import fix_sys_path
+from .fix_path import fix_sys_path
fix_sys_path(__file__)
-from compileapp import run_controller_in, run_view_in
-from languages import translator
+from gluon.compileapp import run_controller_in, run_view_in
+from gluon.languages import translator
from gluon.storage import Storage, List
-import gluon.fileutils
+from gluon import fileutils
from gluon.dal import DAL, Field, Table
from gluon.http import HTTP
@@ -36,13 +36,13 @@ class TestAppAdmin(unittest.TestCase):
from gluon.globals import Request, Response, Session, current
from gluon.html import A, DIV, FORM, MENU, TABLE, TR, INPUT, URL, XML
from gluon.validators import IS_NOT_EMPTY
- from compileapp import LOAD
+ from gluon.compileapp import LOAD
from gluon.http import HTTP, redirect
from gluon.tools import Auth
from gluon.sql import SQLDB
from gluon.sqlhtml import SQLTABLE, SQLFORM
- self.original_check_credentials = gluon.fileutils.check_credentials
- gluon.fileutils.check_credentials = fake_check_credentials
+ self.original_check_credentials = fileutils.check_credentials
+ fileutils.check_credentials = fake_check_credentials
request = Request(env={})
request.application = 'welcome'
request.controller = 'appadmin'
@@ -73,7 +73,7 @@ class TestAppAdmin(unittest.TestCase):
self.env = locals()
def tearDown(self):
- gluon.fileutils.check_credentials = self.original_check_credentials
+ fileutils.check_credentials = self.original_check_credentials
def run_function(self):
return run_controller_in(self.env['request'].controller, self.env['request'].function, self.env)
diff --git a/gluon/tests/test_cache.py b/gluon/tests/test_cache.py
index 31ccd73c..47e11d34 100644
--- a/gluon/tests/test_cache.py
+++ b/gluon/tests/test_cache.py
@@ -6,13 +6,13 @@
"""
import os
import unittest
-from fix_path import fix_sys_path
+from .fix_path import fix_sys_path
fix_sys_path(__file__)
-from storage import Storage
-from cache import CacheInRam, CacheOnDisk, Cache
+from gluon.storage import Storage
+from gluon.cache import CacheInRam, CacheOnDisk, Cache
from gluon.dal import DAL, Field
oldcwd = None
diff --git a/gluon/tests/test_compileapp.py b/gluon/tests/test_compileapp.py
index a57b00c6..2e740e06 100644
--- a/gluon/tests/test_compileapp.py
+++ b/gluon/tests/test_compileapp.py
@@ -4,11 +4,11 @@
""" Unit tests for utils.py """
import unittest
-from fix_path import fix_sys_path
+from .fix_path import fix_sys_path
fix_sys_path(__file__)
-from compileapp import compile_application, remove_compiled_application
+from gluon.compileapp import compile_application, remove_compiled_application
from gluon.fileutils import w2p_pack, w2p_unpack
import os
diff --git a/gluon/tests/test_contenttype.py b/gluon/tests/test_contenttype.py
index 1b40065d..1a476a62 100644
--- a/gluon/tests/test_contenttype.py
+++ b/gluon/tests/test_contenttype.py
@@ -6,12 +6,12 @@
"""
import unittest
-from fix_path import fix_sys_path
+from .fix_path import fix_sys_path
fix_sys_path(__file__)
-from contenttype import contenttype
-
+from gluon.contenttype import contenttype
+from gluon._compat import iteritems
class TestContentType(unittest.TestCase):
@@ -31,7 +31,7 @@ class TestContentType(unittest.TestCase):
'.w2p': 'application/w2p',
'.md': 'text/x-markdown; charset=utf-8'
}
- for k, v in mapping.iteritems():
+ for k, v in iteritems(mapping):
self.assertEqual(contenttype(k), v)
# test without dot extension
diff --git a/gluon/tests/test_contribs.py b/gluon/tests/test_contribs.py
index aefa8fda..bdc2b4e9 100644
--- a/gluon/tests/test_contribs.py
+++ b/gluon/tests/test_contribs.py
@@ -5,13 +5,13 @@
import unittest
import os
-from fix_path import fix_sys_path
+from .fix_path import fix_sys_path
fix_sys_path(__file__)
from gluon.storage import Storage
-import gluon.contrib.fpdf as fpdf
-import gluon.contrib.pyfpdf as pyfpdf
+from gluon.contrib import fpdf as fpdf
+from gluon.contrib import pyfpdf as pyfpdf
from gluon.contrib.appconfig import AppConfig
diff --git a/gluon/tests/test_dal.py b/gluon/tests/test_dal.py
index 7c1f56f6..70fcdf92 100644
--- a/gluon/tests/test_dal.py
+++ b/gluon/tests/test_dal.py
@@ -7,7 +7,7 @@
import sys
import os
import unittest
-from fix_path import fix_sys_path
+from .fix_path import fix_sys_path
fix_sys_path(__file__)
diff --git a/gluon/tests/test_fileutils.py b/gluon/tests/test_fileutils.py
index 91d187d0..cd405b00 100644
--- a/gluon/tests/test_fileutils.py
+++ b/gluon/tests/test_fileutils.py
@@ -3,11 +3,11 @@
import unittest
import datetime
-from fix_path import fix_sys_path
+from .fix_path import fix_sys_path
fix_sys_path(__file__)
-from fileutils import parse_version
+from gluon.fileutils import parse_version
class TestFileUtils(unittest.TestCase):
diff --git a/gluon/tests/test_globals.py b/gluon/tests/test_globals.py
index b6040ac2..736f77e1 100644
--- a/gluon/tests/test_globals.py
+++ b/gluon/tests/test_globals.py
@@ -8,7 +8,7 @@
import re
import unittest
-from fix_path import fix_sys_path
+from .fix_path import fix_sys_path
fix_sys_path(__file__)
diff --git a/gluon/tests/test_html.py b/gluon/tests/test_html.py
index 3ac41f94..7c5d53be 100644
--- a/gluon/tests/test_html.py
+++ b/gluon/tests/test_html.py
@@ -6,16 +6,17 @@
"""
import unittest
-from fix_path import fix_sys_path
+from .fix_path import fix_sys_path
fix_sys_path(__file__)
-from html import *
-from html import verifyURL
-from html import truncate_string
-from storage import Storage
-from html import XML_pickle, XML_unpickle
-from html import TAG_pickler, TAG_unpickler
+from gluon.html import A, ASSIGNJS, B, BEAUTIFY, P, BODY, BR, BUTTON, CAT, CENTER, CODE, COL, COLGROUP, DIV, SPAN, URL, verifyURL
+from gluon.html import truncate_string, EM, FIELDSET, FORM, H1, H2, H3, H4, H5, H6, HEAD, HR, HTML, I, IFRAME, IMG, INPUT, EMBED
+from gluon.html import LABEL, LEGEND, LI, LINK, MARKMIN, MENU, META, OBJECT, OL, OPTGROUP, OPTION, PRE, SCRIPT, SELECT, STRONG
+from gluon.html import STYLE, TABLE, TR, TD, TAG, TBODY, THEAD, TEXTAREA, TFOOT, TH, TITLE, TT, UL, XHTML, XML
+from gluon.storage import Storage
+from gluon.html import XML_pickle, XML_unpickle
+from gluon.html import TAG_pickler, TAG_unpickler
class TestBareHelpers(unittest.TestCase):
@@ -41,7 +42,7 @@ class TestBareHelpers(unittest.TestCase):
self.assertEqual(URL('a', 'static', 'design.css'), '/a/static/design.css')
response = Storage()
response.static_version = '1.2.3'
- from globals import current
+ from gluon.globals import current
current.response = response
self.assertEqual(URL('a', 'static', 'design.css'), '/a/static/design.css')
response.static_version_urls = True
@@ -53,6 +54,8 @@ class TestBareHelpers(unittest.TestCase):
self.assertEqual(URL('a', 'c', 'f', args=['1', '2']), '/a/c/f/1/2')
self.assertEqual(URL('a', 'c', '/f'), '/a/c/f')
self.assertEqual(URL('a', 'c', 'f.json'), '/a/c/f.json')
+ from gluon.globals import current
+ current.request = None
self.assertRaises(SyntaxError, URL, *['a'])
request = Storage()
@@ -61,7 +64,7 @@ class TestBareHelpers(unittest.TestCase):
request.function = 'f'
request.env = {}
- from globals import current # Can't be moved with other import
+ from gluon.globals import current # Can't be moved with other import
current.request = request
must_return = '/a/c/f'
@@ -144,7 +147,7 @@ class TestBareHelpers(unittest.TestCase):
rtn = verifyURL(r)
self.assertEqual(rtn, False)
# emulate user signature
- from globals import current
+ from gluon.globals import current
current.session = Storage(auth=Storage(hmac_key='key'))
r.get_vars['_signature'] = 'a32530f0d0caa80964bb92aad2bedf8a4486a31f'
rtn = verifyURL(r, user_signature=True)
diff --git a/gluon/tests/test_http.py b/gluon/tests/test_http.py
index ee46dc48..a344743b 100644
--- a/gluon/tests/test_http.py
+++ b/gluon/tests/test_http.py
@@ -4,12 +4,12 @@
"""Unit tests for http.py """
import unittest
-from fix_path import fix_sys_path
+from .fix_path import fix_sys_path
fix_sys_path(__file__)
-from http import HTTP, defined_status
+from gluon.http import HTTP, defined_status
class TestHTTP(unittest.TestCase):
diff --git a/gluon/tests/test_is_url.py b/gluon/tests/test_is_url.py
index c655e377..d75724c4 100644
--- a/gluon/tests/test_is_url.py
+++ b/gluon/tests/test_is_url.py
@@ -5,13 +5,13 @@ Unit tests for IS_URL()
"""
import unittest
-from fix_path import fix_sys_path
+from .fix_path import fix_sys_path
fix_sys_path(__file__)
-from validators import IS_URL, IS_HTTP_URL, IS_GENERIC_URL
-from validators import unicode_to_ascii_authority
+from gluon.validators import IS_URL, IS_HTTP_URL, IS_GENERIC_URL
+from gluon.validators import unicode_to_ascii_authority
class TestIsUrl(unittest.TestCase):
@@ -296,7 +296,7 @@ class TestIsGenericUrl(unittest.TestCase):
def testInvalidUrls(self):
urlsToCheckA = []
- for i in range(0, 32) + [127]:
+ for i in list(range(0, 32)) + [127]:
# Control characters are disallowed in any part of a URL
diff --git a/gluon/tests/test_languages.py b/gluon/tests/test_languages.py
index 98d430df..bed7ff42 100644
--- a/gluon/tests/test_languages.py
+++ b/gluon/tests/test_languages.py
@@ -10,12 +10,12 @@ import os
import shutil
import tempfile
import unittest
-from fix_path import fix_sys_path
+from .fix_path import fix_sys_path
fix_sys_path(__file__)
+from gluon import languages
-import languages
MP_WORKING = 0
try:
import multiprocessing
diff --git a/gluon/tests/test_old_doctests.py b/gluon/tests/test_old_doctests.py
index 292c92b2..2da2ed3e 100644
--- a/gluon/tests/test_old_doctests.py
+++ b/gluon/tests/test_old_doctests.py
@@ -9,7 +9,7 @@ import sys
import os
import doctest
import unittest
-from fix_path import fix_sys_path
+from .fix_path import fix_sys_path
fix_sys_path(__file__)
diff --git a/gluon/tests/test_recfile.py b/gluon/tests/test_recfile.py
index ea94c210..07b9b886 100644
--- a/gluon/tests/test_recfile.py
+++ b/gluon/tests/test_recfile.py
@@ -8,7 +8,7 @@ import unittest
import os
import shutil
import uuid
-from fix_path import fix_sys_path
+from .fix_path import fix_sys_path
fix_sys_path(__file__)
diff --git a/gluon/tests/test_router.py b/gluon/tests/test_router.py
index 3d162747..20223048 100644
--- a/gluon/tests/test_router.py
+++ b/gluon/tests/test_router.py
@@ -1044,6 +1044,9 @@ class TestRouter(unittest.TestCase):
'http://domain.com/app2/static/filename-with_underscore'),
norm_root("%s/applications/app2/static/filename-with_underscore" % root))
+ from gluon.globals import current
+ current.response.static_version = None
+
self.assertEqual(str(URL(a='init', c='default', f='a_b')), "/a_b")
self.assertEqual(str(URL(a='app1', c='default', f='a_b')), "/app1/a-b")
self.assertEqual(str(URL(a='app2', c='default', f='a_b')), "/app2/a_b")
diff --git a/gluon/tests/test_routes.py b/gluon/tests/test_routes.py
index 430dc53d..dca0859c 100644
--- a/gluon/tests/test_routes.py
+++ b/gluon/tests/test_routes.py
@@ -65,7 +65,7 @@ def setUpModule():
if not os.path.isdir('gluon'):
os.chdir(os.path.realpath(
'../../')) # run from web2py base directory
- import main # for initialization after chdir
+ from gluon import main # for initialization after chdir
global logger
logger = logging.getLogger('web2py.rewrite')
global_settings.applications_parent = tempfile.mkdtemp()
diff --git a/gluon/tests/test_scheduler.py b/gluon/tests/test_scheduler.py
index fdb75da0..8565b71c 100644
--- a/gluon/tests/test_scheduler.py
+++ b/gluon/tests/test_scheduler.py
@@ -10,7 +10,7 @@ import glob
import datetime
import sys
-from fix_path import fix_sys_path
+from .fix_path import fix_sys_path
fix_sys_path(__file__)
diff --git a/gluon/tests/test_serializers.py b/gluon/tests/test_serializers.py
index 2e543525..0ae8dcb6 100644
--- a/gluon/tests/test_serializers.py
+++ b/gluon/tests/test_serializers.py
@@ -6,14 +6,14 @@
"""
import unittest
-from fix_path import fix_sys_path
+from .fix_path import fix_sys_path
import datetime
import decimal
fix_sys_path(__file__)
-from serializers import *
-from storage import Storage
+from gluon.serializers import *
+from gluon.storage import Storage
# careful with the import path 'cause of isinstance() checks
from gluon.languages import translator
from gluon.html import SPAN
diff --git a/gluon/tests/test_sqlhtml.py b/gluon/tests/test_sqlhtml.py
index 19a60dfb..8e00a197 100644
--- a/gluon/tests/test_sqlhtml.py
+++ b/gluon/tests/test_sqlhtml.py
@@ -8,7 +8,7 @@ import os
import sys
import unittest
-from fix_path import fix_sys_path
+from .fix_path import fix_sys_path
fix_sys_path(__file__)
@@ -18,12 +18,12 @@ DEFAULT_URI = os.getenv('DB', 'sqlite:memory')
from gluon.dal import DAL, Field
from pydal.objects import Table
-from tools import Auth, Mail
+from gluon.tools import Auth, Mail
from gluon.globals import Request, Response, Session
-from storage import Storage
-from languages import translator
+from gluon.storage import Storage
+from gluon.languages import translator
from gluon.http import HTTP
-from validators import *
+from gluon.validators import *
# TODO: Create these test...
diff --git a/gluon/tests/test_storage.py b/gluon/tests/test_storage.py
index 86dcf231..9156ba6a 100644
--- a/gluon/tests/test_storage.py
+++ b/gluon/tests/test_storage.py
@@ -4,13 +4,13 @@
""" Unit tests for storage.py """
import unittest
-from fix_path import fix_sys_path
+from .fix_path import fix_sys_path
fix_sys_path(__file__)
-from storage import Storage, StorageList, List
-from http import HTTP
-import pickle
+from gluon.storage import Storage, StorageList, List
+from gluon.http import HTTP
+from gluon._compat import pickle
class TestStorage(unittest.TestCase):
diff --git a/gluon/tests/test_template.py b/gluon/tests/test_template.py
index 0ef30d0a..26756f33 100644
--- a/gluon/tests/test_template.py
+++ b/gluon/tests/test_template.py
@@ -5,12 +5,12 @@
"""
import unittest
-from fix_path import fix_sys_path
+from .fix_path import fix_sys_path
fix_sys_path(__file__)
-import template
-from template import render
+from gluon import template
+from gluon.template import render
class TestTemplate(unittest.TestCase):
diff --git a/gluon/tests/test_tools.py b/gluon/tests/test_tools.py
index e5ce5ddc..e3091cc0 100644
--- a/gluon/tests/test_tools.py
+++ b/gluon/tests/test_tools.py
@@ -10,7 +10,7 @@ import smtplib
import datetime
import unittest
-from fix_path import fix_sys_path
+from .fix_path import fix_sys_path
fix_sys_path(__file__)
@@ -18,13 +18,12 @@ DEFAULT_URI = os.getenv('DB', 'sqlite:memory')
from gluon.dal import DAL, Field
from pydal.objects import Table
-from tools import Auth, Mail, Recaptcha, Recaptcha2, prettydate
+from gluon.tools import Auth, Mail, Recaptcha, Recaptcha2, prettydate
from gluon.globals import Request, Response, Session
-from storage import Storage
-from languages import translator
+from gluon.storage import Storage
+from gluon.languages import translator
from gluon.http import HTTP
-python_version = sys.version[:3]
IS_IMAP = "imap" in DEFAULT_URI
diff --git a/gluon/tests/test_utils.py b/gluon/tests/test_utils.py
index 1ed800c3..01507cc5 100644
--- a/gluon/tests/test_utils.py
+++ b/gluon/tests/test_utils.py
@@ -4,18 +4,15 @@
""" Unit tests for utils.py """
import unittest
-from fix_path import fix_sys_path
+from .fix_path import fix_sys_path
fix_sys_path(__file__)
-from utils import md5_hash
-from utils import compare
-from utils import is_valid_ip_address
-from utils import web2py_uuid
+from gluon.utils import md5_hash, compare, is_valid_ip_address, web2py_uuid
import hashlib
from hashlib import md5, sha1, sha224, sha256, sha384, sha512
-from utils import simple_hash, get_digest, secure_dumps, secure_loads
+from gluon.utils import simple_hash, get_digest, secure_dumps, secure_loads
class TestUtils(unittest.TestCase):
diff --git a/gluon/tests/test_validators.py b/gluon/tests/test_validators.py
index 6f7034db..69edc37e 100644
--- a/gluon/tests/test_validators.py
+++ b/gluon/tests/test_validators.py
@@ -7,7 +7,7 @@ import unittest
import datetime
import decimal
import re
-from fix_path import fix_sys_path
+from .fix_path import fix_sys_path
fix_sys_path(__file__)
diff --git a/gluon/tests/test_web.py b/gluon/tests/test_web.py
index 33255543..f6e41752 100644
--- a/gluon/tests/test_web.py
+++ b/gluon/tests/test_web.py
@@ -11,12 +11,12 @@ import subprocess
import time
import signal
-from fix_path import fix_sys_path
+from .fix_path import fix_sys_path
fix_sys_path(__file__)
-from contrib.webclient import WebClient
-from urllib2 import HTTPError
+from gluon.contrib.webclient import WebClient
+from gluon._compat import urllib2
webserverprocess = None
@@ -135,7 +135,7 @@ class TestWeb(LiveTest):
s = WebClient('http://127.0.0.1:8000/')
try:
s.post('examples/soap_examples/call/soap', data=xml_request, method="POST")
- except HTTPError as e:
+ except urllib2.HTTPError as e:
assert(e.msg == 'INTERNAL SERVER ERROR')
# check internal server error returned (issue 153)
assert(s.status == 500)
diff --git a/gluon/tools.py b/gluon/tools.py
index df517ebe..1358e67a 100644
--- a/gluon/tools.py
+++ b/gluon/tools.py
@@ -12,12 +12,9 @@ Auth, Mail, PluginManager and various utilities
import base64
from functools import reduce
-try:
- import cPickle as pickle
-except:
- import pickle
+from gluon._compat import pickle, thread, urllib2, Cookie, StringIO, configparser, MIMEBase, MIMEMultipart, \
+ MIMEText, Encoders, Charset, long, urllib_quote
import datetime
-import thread
import logging
import sys
import glob
@@ -28,16 +25,13 @@ import fnmatch
import traceback
import smtplib
import urllib
-import urllib2
-import Cookie
-import cStringIO
-import ConfigParser
import email.utils
import random
import hmac
import hashlib
import json
-from email import MIMEBase, MIMEMultipart, MIMEText, Encoders, Header, message_from_string, Charset
+
+from email import message_from_string
from gluon.contenttype import contenttype
from gluon.storage import Storage, StorageList, Settings, Messages
@@ -184,7 +178,7 @@ class Mail(object):
in this way the can be referenced from the HTML as
etc.
"""
- class Attachment(MIMEBase.MIMEBase):
+ class Attachment(MIMEBase):
"""
Email attachment
@@ -252,7 +246,7 @@ class Mail(object):
content_type = contenttype(filename)
self.my_filename = filename
self.my_payload = payload
- MIMEBase.MIMEBase.__init__(self, *content_type.split('/', 1))
+ MIMEBase.__init__(self, *content_type.split('/', 1))
self.set_payload(payload)
self['Content-Disposition'] = 'attachment; filename="%s"' % filename
if content_id is not None:
@@ -415,7 +409,7 @@ class Mail(object):
def encode_header(key):
if [c for c in key if 32 > ord(c) or ord(c) > 127]:
- return Header.Header(key.encode('utf-8'), 'utf-8')
+ return Header(key.encode('utf-8'), 'utf-8')
else:
return key
@@ -575,7 +569,7 @@ class Mail(object):
# insert the origin payload
payload.attach(payload_in)
# insert the detached signature
- p = MIMEBase.MIMEBase("application", 'pgp-signature')
+ p = MIMEBase("application", 'pgp-signature')
p.set_payload(sig.read())
payload.attach(p)
# it's just a trick to handle the no encryption case
@@ -615,10 +609,10 @@ class Mail(object):
boundary=None,
_subparts=None,
**dict(protocol="application/pgp-encrypted"))
- p = MIMEBase.MIMEBase("application", 'pgp-encrypted')
+ p = MIMEBase("application", 'pgp-encrypted')
p.set_payload("Version: 1\r\n")
payload.attach(p)
- p = MIMEBase.MIMEBase("application", 'octet-stream')
+ p = MIMEBase("application", 'octet-stream')
p.set_payload(cipher.read())
payload.attach(p)
except errors.GPGMEError as ex:
@@ -1881,7 +1875,7 @@ class Auth(object):
'Please ',
A('login',
_href=self.settings.login_url +
- ('?_next=' + urllib.quote(current.request.env.http_web2py_component_location))
+ ('?_next=' + urllib_quote(current.request.env.http_web2py_component_location))
if current.request.env.http_web2py_component_location else ''),
' to view this content.',
_class='not-authorized alert alert-block'))
@@ -2005,7 +1999,7 @@ class Auth(object):
if URL() == action:
next = ''
else:
- next = '?_next=' + urllib.quote(URL(args=request.args,
+ next = '?_next=' + urllib_quote(URL(args=request.args,
vars=request.get_vars))
href = lambda function: \
'%s/%s%s' % (action, function, next if referrer_actions is DEFAULT or function in referrer_actions else '')
@@ -2021,7 +2015,7 @@ class Auth(object):
if self.user_id: # User is logged in
logout_next = self.settings.logout_next
items.append({'name': T('Log Out'),
- 'href': '%s/logout?_next=%s' % (action, urllib.quote(logout_next)),
+ 'href': '%s/logout?_next=%s' % (action, urllib_quote(logout_next)),
'icon': 'icon-off'})
if 'profile' not in self.settings.actions_disabled:
items.append({'name': T('Profile'), 'href': href('profile'),
@@ -2667,7 +2661,8 @@ class Auth(object):
delattr(user, 'password')
else:
user = Row(user)
- for key, value in user.items():
+ for key in list(user.keys()):
+ value = user[key]
if callable(value) or key == 'password':
delattr(user, key)
if self.settings.renew_session_onlogin:
@@ -4259,7 +4254,7 @@ class Auth(object):
next = self.here()
current.session.flash = current.response.flash
return call_or_redirect(self.settings.on_failed_authentication,
- self.settings.login_url + '?_next=' + urllib.quote(next))
+ self.settings.login_url + '?_next=' + urllib_quote(next))
if callable(condition):
flag = condition()
@@ -5277,7 +5272,7 @@ regex_geocode = \
def geocode(address):
try:
- a = urllib.quote(address)
+ a = urllib_quote(address)
txt = fetch('http://maps.googleapis.com/maps/api/geocode/xml?sensor=false&address=%s' % a)
item = regex_geocode.search(txt)
(la, lo) = (float(item.group('la')), float(item.group('lo')))
@@ -5611,7 +5606,7 @@ class Service(object):
import types
r = universal_caller(self.run_procedures[args[0]],
*args[1:], **dict(request.vars))
- s = cStringIO.StringIO()
+ s = StringIO()
if hasattr(r, 'export_to_csv_file'):
r.export_to_csv_file(s)
elif r and not isinstance(r, types.GeneratorType) and isinstance(r[0], (dict, Storage)):
@@ -6968,7 +6963,7 @@ class Config(object):
section,
default_values={}
):
- self.config = ConfigParser.ConfigParser(default_values)
+ self.config = configparser.ConfigParser(default_values)
self.config.read(filename)
if not self.config.has_section(section):
self.config.add_section(section)
diff --git a/gluon/utf8.py b/gluon/utf8.py
index ae592d88..3689d985 100644
--- a/gluon/utf8.py
+++ b/gluon/utf8.py
@@ -11,7 +11,7 @@ Utilities and class for UTF8 strings managing
----------------------------------------------
"""
from __future__ import print_function
-from gluon._compat import builtin as __builtin__
+from gluon._compat import builtin as __builtin__, unicodeT
__all__ = ['Utf8']
@@ -62,7 +62,7 @@ def ord(char):
"""Returns unicode id for utf8 or unicode *char* character
SUPPOSE that *char* is an utf-8 or unicode character only
"""
- if isinstance(char, unicode):
+ if isinstance(char, unicodeT):
return __builtin__.ord(char)
return __builtin__.ord(unicode(char, 'utf-8'))
@@ -119,7 +119,7 @@ class Utf8(str):
You can see the benefit of this class in doctests() below
"""
def __new__(cls, content='', codepage='utf-8'):
- if isinstance(content, unicode):
+ if isinstance(content, unicodeT):
return str.__new__(cls, unicode.encode(content, 'utf-8'))
elif codepage in ('utf-8', 'utf8') or isinstance(content, cls):
return str.__new__(cls, content)
diff --git a/gluon/validators.py b/gluon/validators.py
index e2ed96a8..b307c869 100644
--- a/gluon/validators.py
+++ b/gluon/validators.py
@@ -21,7 +21,7 @@ import urllib
import struct
import decimal
import unicodedata
-from gluon._compat import StringIO, long, unicodeT
+from gluon._compat import StringIO, long, unicodeT, to_unicode, urllib_unquote, unichr, to_bytes
from gluon.utils import simple_hash, web2py_uuid, DIGEST_ALG_BY_SIZE
from pydal.objects import Field, FieldVirtual, FieldMethod
from functools import reduce
@@ -1508,7 +1508,7 @@ def unicode_to_ascii_url(url, prepend_scheme):
# Try appending a scheme to see if that fixes the problem
scheme_to_prepend = prepend_scheme or 'http'
groups = url_split_regex.match(
- unicode(scheme_to_prepend) + u'://' + url).groups()
+ to_unicode(scheme_to_prepend) + u'://' + url).groups()
# if we still can't find the authority
if not groups[3]:
raise Exception('No authority component found, ' +
@@ -1609,7 +1609,7 @@ class IS_GENERIC_URL(Validator):
scheme = url_split_regex.match(value).group(2)
# Clean up the scheme before we check it
if not scheme is None:
- scheme = urllib.unquote(scheme).lower()
+ scheme = urllib_unquote(scheme).lower()
# If the scheme really exists
if scheme in self.allowed_schemes:
# Then the URL is valid
diff --git a/gluon/widget.py b/gluon/widget.py
index 6242c280..17203388 100644
--- a/gluon/widget.py
+++ b/gluon/widget.py
@@ -55,9 +55,9 @@ def run_system_tests(options):
import subprocess
major_version = sys.version_info[0]
minor_version = sys.version_info[1]
+ call_args = [sys.executable, '-m', 'unittest', '-v', 'gluon.tests']
if major_version == 2:
if minor_version in (7,):
- call_args = [sys.executable, '-m', 'unittest', '-v', 'gluon.tests']
if options.with_coverage:
try:
import coverage
@@ -76,8 +76,8 @@ def run_system_tests(options):
sys.stderr.write("unknown python 2.x version\n")
ret = 256
else:
- sys.stderr.write("Only Python 2.x supported.\n")
- ret = 256
+ sys.stderr.write("Experimental Python 3.x.\n")
+ ret = subprocess.call(call_args)
sys.exit(ret and 1)