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)