[a-zA-Z][_a-zA-Z\-\d]*)(?:\[(?P[^\]]*)\])?)?)',re.S)
+regex_code = re.compile('('+META+'|'+DISABLED_META+r'|````)|(``(?P.+?)``(?::(?P[a-zA-Z][_a-zA-Z\-\d]*)(?:\[(?P[^\]]*)\])?)?)',re.S)
regex_strong=re.compile(r'\*\*(?P[^\s*]+( +[^\s*]+)*)\*\*')
regex_del=re.compile(r'~~(?P[^\s*]+( +[^\s*]+)*)~~')
regex_em=re.compile(r"''(?P[^\s']+(?: +[^\s']+)*)''")
regex_num=re.compile(r"^\s*[+-]?((\d+(\.\d*)?)|\.\d+)([eE][+-]?[0-9]+)?\s*$")
-regex_list=re.compile('^(?:(#{1,6}|\.+ |\++ |\++\. |\-+ |\-+\. )\s*)?(.*)$')
+regex_list=re.compile('^(?:(?:(#{1,6})|(?:(\.+|\++|\-+)(\.)?))\s+)?(.*)$')
regex_bq_headline=re.compile('^(?:(\.+|\++|\-+)(\.)?\s+)?(-{3}-*)$')
regex_tq=re.compile('^(-{3}-*)(?::(?P[a-zA-Z][_a-zA-Z\-\d]*)(?:\[(?P[a-zA-Z][_a-zA-Z\-\d]*)\])?)?$')
regex_proto = re.compile(r'(?/=])(?P
\w+):(?P\w+://[\w\d\-+=?%&/:.]+)', re.M)
-regex_auto = re.compile(r'(?/=])(?P\w+://[^\s\'\"\]\}\)]+)',re.M)
+regex_auto = re.compile(r'(?/=])(?P\w+://[\w\d\-+_=?%&/:.]+)',re.M)
regex_link=re.compile(r'('+LINK+r')|\[\[(?P.+?)\]\]')
regex_link_level2=re.compile(r'^(?P\S.*?)?(?:\s+\[(?P.+?)\])?(?:\s+(?P\S+))?(?:\s+(?Ppopup))?\s*$')
regex_media_level2=re.compile(r'^(?P\S.*?)?(?:\s+\[(?P.+?)\])?(?:\s+(?P\S+))?\s+(?Pimg|IMG|left|right|center|video|audio)(?:\s+(?P\d+px))?\s*$')
@@ -579,7 +600,8 @@ def render(text,
autolinks='default',
protolinks='default',
class_prefix='',
- id_prefix='markmin_'):
+ id_prefix='markmin_',
+ pretty_print=False):
"""
Arguments:
- text is the text to be processed
@@ -598,7 +620,7 @@ def render(text,
- class_prefix is a prefix for ALL classes in markmin text. E.g. if class_prefix='my_'
then for ``test``:cls class will be changed to "my_cls" (default value is '')
- id_prefix is prefix for ALL ids in markmin text (default value is 'markmin_'). E.g.:
- -- [[id]] will be converted to
+ -- [[id]] will be converted to
-- [[link #id]] will be converted to link
-- ``test``:cls[id] will be converted to test
@@ -791,9 +813,18 @@ def render(text,
>>> render('[[id1 [span **messag** in ''markmin''] ]] ... [[**link** to id [link\\\'s title] #mark1]]')
'span messag in markmin
... link to id
'
+ >>> render('# Multiline[[NEWLINE]]\\n title\\nParagraph[[NEWLINE]]\\nwith breaks[[NEWLINE]]\\nin it')
+ 'Multiline
title
Paragraph
with breaks
in it
'
+
+ >>> render("anchor with name 'NEWLINE': [[NEWLINE [ ] ]]")
+ 'anchor with name \\'NEWLINE\\':
'
+
+ >>> render("anchor with name 'NEWLINE': [[NEWLINE [newline] ]]")
+ 'anchor with name \\'NEWLINE\\':
newline
'
"""
if autolinks=="default": autolinks = autolinks_simple
if protolinks=="default": protolinks = protolinks_simple
+ pp='\n' if pretty_print else ''
text = str(text or '')
text = regex_backslash.sub(lambda m: m.group(1).translate(ttab_in), text)
@@ -801,11 +832,22 @@ def render(text,
# this is experimental @{function/args}
# turns into a digitally signed URL
def u1(match,URL=URL):
- a,c,f,args = match.group('a','c','f','args')
+ a,c,f,args = match.group('a','c','f','args')
return URL(a=a or None,c=c or None,f = f or None,
args=args.split('/'), scheme=True, host=True)
text = regex_URL.sub(u1,text)
+ if environment:
+ def u2(match, environment=environment):
+ f = environment.get(match.group('a'), match.group(0))
+ if callable(f):
+ try:
+ f = f(match.group('b'))
+ except Exception, e:
+ f = 'ERROR: %s' % e
+ return str(f)
+ text = regex_env.sub(u2, text)
+
if latex == 'google':
text = regex_dd.sub('``\g``:latex ', text)
@@ -816,9 +858,12 @@ def render(text,
segments = []
def mark_code(m):
g = m.group(0)
- if m.group() in ( META, DISABLED_META ):
+ if g in (META, DISABLED_META ):
segments.append((None, None, None, g))
return m.group()
+ elif g == '````':
+ segments.append((None, None, None, ''))
+ return m.group()
else:
c = m.group('c') or ''
p = m.group('p') or ''
@@ -849,13 +894,13 @@ def render(text,
#############################################################
# normalize spaces
#############################################################
- strings=[t.strip() for t in text.split('\n')]
+ strings=text.split('\n')
def parse_title(t, s): #out, lev, etags, tag, s):
hlevel=str(len(t))
out.extend(etags[::-1])
out.append("%s"%(hlevel,s))
- etags[:]=[""%hlevel]
+ etags[:]=["%s"%(hlevel,pp)]
lev=0
ltags[:]=[]
tlev[:]=[]
@@ -880,8 +925,8 @@ def render(text,
out.append(etags.pop())
ltags.pop()
for i in xrange(lent-lev):
- out.append('<'+tag+'>')
- etags.append(''+tag+'>')
+ out.append('<'+tag+'>'+pp)
+ etags.append(''+tag+'>'+pp)
lev+=1
ltags.append(lev)
tlev.append(tag)
@@ -892,8 +937,8 @@ def render(text,
ltags.pop()
out.append(etags.pop())
tlev[-1]=tag
- out.append('<'+tag+'>')
- etags.append(''+tag+'>')
+ out.append('<'+tag+'>'+pp)
+ etags.append(''+tag+'>'+pp)
ltags.append(lev)
else:
if ltags.count(lev)>1:
@@ -901,7 +946,7 @@ def render(text,
ltags.pop()
mtag='l'
out.append('')
- etags.append('')
+ etags.append(''+pp)
ltags.append(lev)
if s[:1] == '-':
(s, mtag, lineno) = parse_table_or_blockquote(s, mtag, lineno)
@@ -954,7 +999,7 @@ def render(text,
return (s, mtag, lineno)
lineno+=1
- s = strings[lineno]
+ s = strings[lineno].strip()
if s:
if '|' in s:
# table
@@ -967,7 +1012,7 @@ def render(text,
# parse table:
while lineno < strings_len:
- s = strings[lineno]
+ s = strings[lineno].strip()
if s[:1] == '=':
if s.count('=')==len(s) and len(s)>3: # header or footer
if not thead: # if thead list is empty:
@@ -994,7 +1039,7 @@ def render(text,
if regex_num.match(f)
else '',
f.strip()
- ) for f in s.split('|')])+'')
+ ) for f in s.split('|')])+''+pp)
rownum+=1
lineno+=1
@@ -1002,15 +1047,15 @@ def render(text,
t_id = ' id="%s%s"'%(id_prefix, t_id) if t_id else ''
s = ''
if thead:
- s += ''+''.join([l for l in thead])+''
+ s += ''+pp+''.join([l for l in thead])+''+pp
if not tbody: # tbody strings are in tout list
tbody = tout
tout = []
if tbody: # if tbody list is not empty:
- s += ''+''.join([l for l in tbody])+''
+ s += ''+pp+''.join([l for l in tbody])+''+pp
if tout: # tfoot is not empty:
- s += ''+''.join([l for l in tout])+''
- s = '' % (t_cls, t_id, s)
+ s += ''+pp+''.join([l for l in tout])+''+pp
+ s = '%s' % (t_cls, t_id, pp, s, pp)
mtag='t'
else:
# parse blockquote:
@@ -1021,7 +1066,7 @@ def render(text,
# search blockquote closing line:
while lineno < strings_len:
- s = strings[lineno]
+ s = strings[lineno].strip()
if not t_mode:
m = regex_tq.match(s)
if m:
@@ -1031,7 +1076,7 @@ def render(text,
break
if regex_bq_headline.match(s):
- if lineno+1 < strings_len and strings[lineno+1]:
+ if lineno+1 < strings_len and strings[lineno+1].strip():
t_mode = True
lineno+=1
continue
@@ -1044,7 +1089,7 @@ def render(text,
t_cls = ' class="%s%s"'%(class_prefix,t_cls) if t_cls and t_cls != 'id' else ''
t_id = ' id="%s%s"'%(id_prefix,t_id) if t_id else ''
- s = '%s
' \
+ s = '%s
%s' \
% (t_cls,
t_id,
render('\n'.join(strings[bq_begin:lineno]),
@@ -1057,7 +1102,9 @@ def render(text,
autolinks,
protolinks,
class_prefix,
- id_prefix)
+ id_prefix,
+ pretty_print),
+ pp
)
mtag='q'
else:
@@ -1068,53 +1115,58 @@ def render(text,
if sep == 'p':
pbeg = ""
- pend = "
"
+ pend = "
"+pp
br = ''
else:
pbeg = pend = ''
- br = "
" if sep=='br' else ''
+ br = "
"+pp if sep=='br' else ''
- lev = 0 # рівень вкладеності списків
- c0 = '' # перший символ поточного рядка
- out = [] # результуючий список рядків
- etags = [] # завершуючі таги
- ltags = [] # номер рівня відповідний завершуючому тагу
- tlev = [] # таг рівня ('ul' або 'ol')
- mtag = '' # marked tag (~last tag) ('l','.','h','p','t'). Used for set
- # and for avoid around tables and blockquotes
+ lev = 0 # nesting level of lists
+ c0 = '' # first character of current line
+ out = [] # list of processed lines
+ etags = [] # trailing tags
+ ltags = [] # level# correspondent to trailing tag
+ tlev = [] # list of tags for each level ('ul' or 'ol')
+ mtag = '' # marked tag (~last tag) ('l','.','h','p','t'). Used to set
+ # and to avoid around tables and blockquotes
lineno = 0
strings_len = len(strings)
while lineno < strings_len:
- s = strings[lineno]
+ s0 = strings[lineno][:1]
+ s = strings[lineno].strip()
""" # + - . ---------------------
## ++ -- .. ------- field | field | field <-title
### +++ --- ... quote =====================
#### ++++ ---- .... ------- field | field | field <-body
##### +++++ ----- ..... ---------------------:class[id]
"""
- pc0=c0 # перший символ попереднього рядка
+ pc0=c0 # first character of previous line
c0=s[:1]
if c0: # for non empty strings
if c0 in "#+-.": # first character is one of: # + - .
- match = regex_list.search(s)
- (t,p,s) = match.group(1), None, match.group(2)
- t = (t or '').strip()
- if t.endswith('.'): t, p = t[:-1], '.'
- # t - tag ("###", "+++", "---", "...")
+ (t1,t2,p,ss) = regex_list.findall(s)[0]
+ # t1 - tag ("###")
+ # t2 - tag ("+++", "---", "...")
# p - paragraph point ('.')->for "++." or "--."
- # s - other part of string
- if t:
+ # ss - other part of string
+ if t1 or t2:
# headers and lists:
if c0 == '#': # headers
- (lev, mtag) = parse_title(t, s)
+ (lev, mtag) = parse_title(t1, ss)
+ lineno+=1
+ continue
elif c0 == '+': # ordered list
- (lev, mtag, lineno)= parse_list(t, p, s, 'ol', lev, mtag, lineno)
+ (lev, mtag, lineno)= parse_list(t2, p, ss, 'ol', lev, mtag, lineno)
+ lineno+=1
+ continue
elif c0 == '-': # unordered list
- (lev, mtag, lineno) = parse_list(t, p, s, 'ul', lev, mtag, lineno)
- else: # c0 == '.' # paragraph in lists
- (lev, mtag, lineno) = parse_point(t, s, lev, mtag, lineno)
- lineno+=1
- continue
+ (lev, mtag, lineno) = parse_list(t2, p, ss, 'ul', lev, mtag, lineno)
+ lineno+=1
+ continue
+ elif lev>0: # and c0 == '.' # paragraph in lists
+ (lev, mtag, lineno) = parse_point(t2, ss, lev, mtag, lineno)
+ lineno+=1
+ continue
else:
if c0 == '-': # table or blockquote?
(s, mtag, lineno) = parse_table_or_blockquote(s, mtag, lineno)
@@ -1123,7 +1175,7 @@ def render(text,
# new paragraph
pc0=''
- if pc0 == '':
+ if pc0 == '' or (mtag != 'p' and s0 not in (' ','\t')):
# paragraph
out.extend(etags[::-1])
etags=[]
@@ -1171,12 +1223,12 @@ def render(text,
style = p_begin = p_end = ''
if p == 'center':
p_begin = ''
- p_end = '
'
+ p_end = '
'+pp
elif p in ('left','right'):
style = ' style="float:%s"' % p
if p in ('video','audio'):
t = render(t, {}, {}, 'br', URL, environment, latex,
- autolinks, protolinks, class_prefix, id_prefix)
+ autolinks, protolinks, class_prefix, id_prefix, pretty_print)
return '<%(p)s controls="controls"%(title)s%(width)s>%(t)s%(p)s>' \
% dict(p=p, title=title, width=width, k=k, t=t)
alt = ' alt="%s"'%escape(t).replace(META, DISABLED_META) if t else ''
@@ -1197,13 +1249,16 @@ def render(text,
title = ' title="%s"' % a.replace(META, DISABLED_META) if a else ''
target = ' target="_blank"' if p == 'popup' else ''
t = render(t, {}, {}, 'br', URL, environment, latex, autolinks,
- protolinks, class_prefix, id_prefix) if t else k
+ protolinks, class_prefix, id_prefix, pretty_print) if t else k
return '%(t)s' \
% dict(k=k, title=title, target=target, t=t)
+ if t == 'NEWLINE' and not a:
+ return '
'+pp
return '%s
' % (escape(id_prefix+t),
- render(a, {},{},'br', URL,
- environment, latex, autolinks,
- protolinks, class_prefix, id_prefix))
+ render(a, {},{},'br', URL,
+ environment, latex, autolinks,
+ protolinks, class_prefix,
+ id_prefix, pretty_print))
parts = text.split(LINK)
text = parts[0]
@@ -1230,9 +1285,9 @@ def render(text,
if code[:1]=='\n': code=code[1:]
if code[-1:]=='\n': code=code[:-1]
if p:
- return extra[b](code,p)
+ return str(extra[b](code,p))
else:
- return extra[b](code)
+ return str(extra[b](code))
elif b=='cite':
return '['+','.join('%s' \
% (d,b,d) \
@@ -1241,41 +1296,37 @@ def render(text,
return LATEX % code.replace('"','\"').replace('\n',' ')
elif b in html_colors:
return '%s' \
- % (b, render(code,{},{},'br',URL,environment,latex,autolinks, protolinks))
+ % (b, render(code, {}, {}, 'br', URL, environment, latex,
+ autolinks, protolinks, class_prefix, id_prefix, pretty_print))
elif b in ('c', 'color') and p:
c=p.split(':')
fg='color: %s;' % c[0] if c[0] else ''
bg='background-color: %s;' % c[1] if len(c)>1 and c[1] else ''
return '%s' \
- % (fg, bg, render(code,{},{},'br', URL, environment, latex, autolinks, protolinks))
+ % (fg, bg, render(code, {}, {}, 'br', URL, environment, latex,
+ autolinks, protolinks, class_prefix, id_prefix, pretty_print))
cls = ' class="%s%s"'%(class_prefix,b) if b and b != 'id' else ''
id = ' id="%s%s"'%(id_prefix,escape(p)) if p else ''
beg=(code[:1]=='\n')
end=[None,-1][code[-1:]=='\n']
if beg and end:
- return '%s
' % (cls, id, escape(code[1:-1]))
+ return '%s
%s' % (cls, id, escape(code[1:-1]), pp)
return '%s' % (cls, id, escape(code[beg:end]))
-
text = regex_expand_meta.sub(expand_meta, text)
text = text.translate(ttab_out)
-
- if environment:
- def u2(match, environment=environment):
- f = environment.get(match.group('a'), match.group(0))
- if callable(f):
- try:
- f = f(match.group('b'))
- except Exception, e:
- f = 'ERROR: %s' % e
- return str(f)
- text = regex_env.sub(u2, text)
-
return text
def markmin2html(text, extra={}, allowed={}, sep='p',
- autolinks='default',protolinks='default'):
+ autolinks='default', protolinks='default',
+ class_prefix='', id_prefix='markmin_', pretty_print=False):
return render(text, extra, allowed, sep,
- autolinks=autolinks, protolinks=protolinks)
+ autolinks=autolinks, protolinks=protolinks,
+ class_prefix=class_prefix, id_prefix=id_prefix,
+ pretty_print=pretty_print)
+
+def run_doctests():
+ import doctest
+ doctest.testmod()
if __name__ == '__main__':
import sys
@@ -1312,7 +1363,9 @@ if __name__ == '__main__':
pre { background-color: #E0E0E0; padding: 5px; }
""")[1:]
- print html % dict(title="Markmin markup language", style=style, body=markmin2html(__doc__))
+ print html % dict(title="Markmin markup language",
+ style=style,
+ body=markmin2html(__doc__, pretty_print=True))
elif sys.argv[1:2] == ['-t']:
from timeit import Timer
loops=1000
@@ -1338,7 +1391,8 @@ if __name__ == '__main__':
else:
markmin_style = ""
- print html % dict(title=sys.argv[1], style=markmin_style, body=markmin2html(markmin_text))
+ print html % dict(title=sys.argv[1], style=markmin_style,
+ body=markmin2html(markmin_text, pretty_print=True))
finally:
fargv.close()
@@ -1348,4 +1402,4 @@ if __name__ == '__main__':
print " -t - timeit __doc__ (for testing purpuse only)"
print " file.markmin [file.css] - process file.markmin + built in file.css (optional)"
print " file.markmin [@path_to/css] - process file.markmin + link path_to/css (optional)"
- doctest.testmod()
+ run_doctests()
diff --git a/gluon/contrib/plural_rules/__init__.py b/gluon/contrib/plural_rules/__init__.py
new file mode 100644
index 00000000..8b137891
--- /dev/null
+++ b/gluon/contrib/plural_rules/__init__.py
@@ -0,0 +1 @@
+
diff --git a/gluon/contrib/rules/plural_rules-af.py b/gluon/contrib/plural_rules/af.py
similarity index 100%
rename from gluon/contrib/rules/plural_rules-af.py
rename to gluon/contrib/plural_rules/af.py
diff --git a/gluon/contrib/rules/plural_rules-bg.py b/gluon/contrib/plural_rules/bg.py
similarity index 100%
rename from gluon/contrib/rules/plural_rules-bg.py
rename to gluon/contrib/plural_rules/bg.py
diff --git a/gluon/contrib/rules/plural_rules-cs.py b/gluon/contrib/plural_rules/cs.py
similarity index 100%
rename from gluon/contrib/rules/plural_rules-cs.py
rename to gluon/contrib/plural_rules/cs.py
diff --git a/gluon/contrib/rules/plural_rules-de.py b/gluon/contrib/plural_rules/de.py
similarity index 100%
rename from gluon/contrib/rules/plural_rules-de.py
rename to gluon/contrib/plural_rules/de.py
diff --git a/gluon/contrib/rules/plural_rules-en.py b/gluon/contrib/plural_rules/en.py
similarity index 100%
rename from gluon/contrib/rules/plural_rules-en.py
rename to gluon/contrib/plural_rules/en.py
diff --git a/gluon/contrib/rules/plural_rules-es.py b/gluon/contrib/plural_rules/es.py
similarity index 100%
rename from gluon/contrib/rules/plural_rules-es.py
rename to gluon/contrib/plural_rules/es.py
diff --git a/gluon/contrib/rules/plural_rules-fr.py b/gluon/contrib/plural_rules/fr.py
similarity index 100%
rename from gluon/contrib/rules/plural_rules-fr.py
rename to gluon/contrib/plural_rules/fr.py
diff --git a/gluon/contrib/rules/plural_rules-he.py b/gluon/contrib/plural_rules/he.py
similarity index 100%
rename from gluon/contrib/rules/plural_rules-he.py
rename to gluon/contrib/plural_rules/he.py
diff --git a/gluon/contrib/rules/plural_rules-hi.py b/gluon/contrib/plural_rules/hi.py
similarity index 100%
rename from gluon/contrib/rules/plural_rules-hi.py
rename to gluon/contrib/plural_rules/hi.py
diff --git a/gluon/contrib/rules/plural_rules-hu.py b/gluon/contrib/plural_rules/hu.py
similarity index 100%
rename from gluon/contrib/rules/plural_rules-hu.py
rename to gluon/contrib/plural_rules/hu.py
diff --git a/gluon/contrib/rules/plural_rules-it.py b/gluon/contrib/plural_rules/it.py
similarity index 100%
rename from gluon/contrib/rules/plural_rules-it.py
rename to gluon/contrib/plural_rules/it.py
diff --git a/gluon/contrib/rules/plural_rules-ja.py b/gluon/contrib/plural_rules/ja.py
similarity index 100%
rename from gluon/contrib/rules/plural_rules-ja.py
rename to gluon/contrib/plural_rules/ja.py
diff --git a/gluon/contrib/rules/plural_rules-lt.py b/gluon/contrib/plural_rules/lt.py
similarity index 100%
rename from gluon/contrib/rules/plural_rules-lt.py
rename to gluon/contrib/plural_rules/lt.py
diff --git a/gluon/contrib/rules/plural_rules-pl.py b/gluon/contrib/plural_rules/pl.py
similarity index 100%
rename from gluon/contrib/rules/plural_rules-pl.py
rename to gluon/contrib/plural_rules/pl.py
diff --git a/gluon/contrib/rules/plural_rules-pt.py b/gluon/contrib/plural_rules/pt.py
similarity index 100%
rename from gluon/contrib/rules/plural_rules-pt.py
rename to gluon/contrib/plural_rules/pt.py
diff --git a/gluon/contrib/rules/plural_rules-ro.py b/gluon/contrib/plural_rules/ro.py
similarity index 100%
rename from gluon/contrib/rules/plural_rules-ro.py
rename to gluon/contrib/plural_rules/ro.py
diff --git a/gluon/contrib/rules/plural_rules-ru.py b/gluon/contrib/plural_rules/ru.py
similarity index 100%
rename from gluon/contrib/rules/plural_rules-ru.py
rename to gluon/contrib/plural_rules/ru.py
diff --git a/gluon/contrib/rules/plural_rules-sk.py b/gluon/contrib/plural_rules/sk.py
similarity index 100%
rename from gluon/contrib/rules/plural_rules-sk.py
rename to gluon/contrib/plural_rules/sk.py
diff --git a/gluon/contrib/rules/plural_rules-sl.py b/gluon/contrib/plural_rules/sl.py
similarity index 100%
rename from gluon/contrib/rules/plural_rules-sl.py
rename to gluon/contrib/plural_rules/sl.py
diff --git a/gluon/contrib/rules/plural_rules-tr.py b/gluon/contrib/plural_rules/tr.py
similarity index 100%
rename from gluon/contrib/rules/plural_rules-tr.py
rename to gluon/contrib/plural_rules/tr.py
diff --git a/gluon/contrib/rules/plural_rules-uk.py b/gluon/contrib/plural_rules/uk.py
similarity index 100%
rename from gluon/contrib/rules/plural_rules-uk.py
rename to gluon/contrib/plural_rules/uk.py
diff --git a/gluon/contrib/rules/plural_rules-zh.py b/gluon/contrib/plural_rules/zh.py
similarity index 100%
rename from gluon/contrib/rules/plural_rules-zh.py
rename to gluon/contrib/plural_rules/zh.py
diff --git a/gluon/contrib/webclient.py b/gluon/contrib/webclient.py
new file mode 100644
index 00000000..b4ab242b
--- /dev/null
+++ b/gluon/contrib/webclient.py
@@ -0,0 +1,205 @@
+"""
+Developed by Massimo Di Pierro
+Released under the web2py license (LGPL)
+
+It an interface on top of urllib2 which simplifies scripting of http requests
+mostly for testing purposes
+
+- customizable
+- supports basic auth
+- supports cookies
+- supports session cookies (tested with web2py sessions)
+- detects broken session
+- detects web2py form postbacks and handles formname and formkey
+- detects web2py tickets
+
+Some examples at the bottom.
+"""
+
+import re
+import time
+import urllib
+import urllib2
+
+DEFAULT_HEADERS = {
+ 'user-agent': 'Mozilla/4.0', # some servers are picky
+ 'accept-language': 'en',
+ }
+
+FORM_REGEX = re.compile('(\.+?)" \/\>)?\.+?)" \/\>')
+
+SESSION_REGEX = 'session_id_(?P.+)'
+
+class WebClient(object):
+
+ def __init__(self,
+ app = '',
+ postbacks = True,
+ default_headers = DEFAULT_HEADERS,
+ session_regex = SESSION_REGEX):
+ self.app = app
+ self.postbacks = postbacks
+ self.forms = {}
+ self.history = []
+ self.cookies = {}
+ self.default_headers = default_headers
+ self.sessions = {}
+ self.session_regex = session_regex and re.compile(session_regex)
+
+ def get(self,url,cookies=None,headers=None,auth=None):
+ return self.post(url,data=None,cookies=cookies,headers=headers)
+
+ def post(self,url,data=None,cookies=None,headers=None,auth=None):
+ self.url = self.app+url
+
+ # if this POST form requires a postback do it
+ if data and '_formname' in data and self.postbacks and \
+ self.history and self.history[-1][1]!=self.url:
+ # to bypass the web2py CSRF need to get formkey
+ # before submitting the form
+ self.get(url,cookies=cookies,headers=headers,auth=auth)
+
+ # unless cookies are specified, recycle cookies
+ if cookies is None:
+ cookies = self.cookies
+ cookies = cookies or {}
+ headers = headers or {}
+
+ # if required do basic auth
+ if auth:
+ auth_handler = urllib2.HTTPBasicAuthHandler()
+ auth_handler.add_password(**auth)
+ opener = urllib2.build_opener(auth_handler)
+ else:
+ opener = urllib2.build_opener()
+
+ # copy headers from dict to list of key,value
+ headers_list = []
+ for key,value in self.default_headers.iteritems():
+ if not key in headers:
+ headers[key] = value
+ for key,value in headers.iteritems():
+ if isinstance(value,(list,tuple)):
+ for v in value: headers_list.append((key,v))
+ else:
+ headers_list.append((key,value))
+
+ # move cookies to headers
+ for key,value in cookies.iteritems():
+ headers_list.append(('Cookie','%s=%s' % (key,value)))
+
+ # add headers to request
+ for key,value in headers_list:
+ opener.addheaders.append((key,str(value)))
+
+ # assume everything is ok and make http request
+ error = None
+ try:
+ if data is not None:
+ self.method = 'POST'
+
+ # if there is only one form, set _formname automatically
+ if not '_formname' in data and len(self.forms)==1:
+ data['_formname'] = self.forms.keys()[0]
+
+ # if there is no formkey but it is known, set it
+ if '_formname' in data and not '_formkey' in data and \
+ data['_formname'] in self.forms:
+ data['_formkey'] = self.forms[data['_formname']]
+
+ # time the POST request
+ data = urllib.urlencode(data)
+ t0 = time.time()
+ self.response = opener.open(self.url,data)
+ self.time = time.time()-t0
+ else:
+ self.method = 'GET'
+
+ # time the GET request
+ t0 = time.time()
+ self.response = opener.open(self.url)
+ self.time = time.time()-t0
+ except urllib2.HTTPError, error:
+ # catch HTTP errors
+ self.time = time.time()-t0
+ self.response = error
+
+ self.status = self.response.getcode()
+ self.text = self.response.read()
+ self.headers = dict(self.response.headers)
+
+ # treat web2py tickets as special types of errors
+ if error is not None:
+ if 'web2py_error' in self.headers:
+ raise RuntimeError, self.headers['web2py_error']
+ else:
+ raise error
+
+ # parse headers into cookies
+ if 'set-cookie' in self.headers:
+ self.cookies = dict(
+ item[:item.find(';')].split('=') for item in \
+ self.headers['set-cookie'].split(','))
+ else:
+ self.cookies = {}
+
+ # check is a new session id has been issued, symptom of broken session
+ if self.session_regex is not None:
+ for cookie, value in self.cookies.iteritems():
+ match = self.session_regex.match(cookie)
+ if match:
+ name = match.group('name')
+ if name in self.sessions and self.sessions[name]!=value:
+ raise RuntimeError, 'Broken sessions %s' % name
+ self.sessions[name] = value
+
+ # find all forms and formkeys in page
+ self.forms = {}
+ for match in FORM_REGEX.finditer(self.text):
+ self.forms[match.group('formname')] = match.group('formkey')
+
+ # log this request
+ self.history.append((self.method,self.url,self.status,self.time))
+
+def test_web2py_registration_and_login():
+ # from gluon.contrib.webclient import WebClient
+ # start a web2py instance for testing
+
+ client = WebClient('http://127.0.0.1:8000/welcome/default/')
+ client.get('index')
+
+ # register
+ data = dict(first_name = 'Homer',
+ last_name = 'Simpson',
+ email = 'homer@web2py.com',
+ password = 'test',
+ password_two = 'test',
+ _formname = 'register')
+ client.post('user/register',data = data)
+
+ # logout
+ client.get('user/logout')
+
+ # login
+ data = dict(email='homer@web2py.com',
+ password='test',
+ _formname = 'login')
+ client.post('user/login',data = data)
+
+ # check registration and login were successful
+ client.get('user/profile')
+ assert 'Welcome Homer' in client.text
+
+ # print some variables
+ print '\nsessions:\n',client.sessions
+ print '\nheaders:\n',client.headers
+ print '\ncookies:\n',client.cookies
+ print '\nforms:\n',client.forms
+ print
+ for method, url, status, t in client.history:
+ print method, url, status, t
+
+if __name__ == '__main__':
+ test_web2py_registration_and_login()
+
+
diff --git a/gluon/dal.py b/gluon/dal.py
index 5f9b5b0e..5064fb47 100644
--- a/gluon/dal.py
+++ b/gluon/dal.py
@@ -20,6 +20,7 @@ including:
- SQLite & SpatiaLite
- MySQL
- Postgres
+- Firebird
- Oracle
- MS SQL
- DB2
@@ -226,19 +227,25 @@ thread = threading.local()
# internal representation of tables with field
# ., tables and fields may only be [a-zA-Z0-9_]
-regex_type = re.compile('^([\w\_\:]+)')
-regex_dbname = re.compile('^(\w+)(\:\w+)*')
-regex_safe = re.compile('^\w+$')
-regex_table_field = re.compile('^(\w+)\.(\w+)$')
-regex_content = re.compile('(?P[\w\-]+)\.(?P[\w\-]+)\.(?P[\w\-]+)\.(?P\w+)\.\w+$')
-regex_cleanup_fn = re.compile('[\'"\s;]+')
-string_unpack=re.compile('(?[\w\-]+)\.(?P[\w\-]+)\.(?P[\w\-]+)\.(?P\w+)\.\w+$')
+REGEX_CLEANUP_FN = re.compile('[\'"\s;]+')
+REGEX_UNPACK = re.compile('(?\w{1,5})$')
+REGEX_QUOTES = re.compile("'[^']*'")
+REGEX_ALPHANUMERIC = re.compile('^[a-zA-Z]\w*$')
# list of drivers will be built on the fly
# and lists only what is available
-drivers = []
+DRIVERS = []
try:
from new import classobj
@@ -246,22 +253,23 @@ try:
from google.appengine.api import namespace_manager, rdbms
from google.appengine.api.datastore_types import Key ### for belongs on ID
from google.appengine.ext.db.polymodel import PolyModel
- drivers.append('google')
+ DRIVERS.append('google')
except ImportError:
pass
-if not 'google' in drivers:
+if not 'google' in DRIVERS:
try:
- # first try pysqlite2 else try sqlite3
- try:
- from pysqlite2 import dbapi2 as sqlite3
- drivers.append('pysqlite2')
- except ImportError:
- from sqlite3 import dbapi2 as sqlite3
- drivers.append('SQLite3')
+ from pysqlite2 import dbapi2 as sqlite2
+ DRIVERS.append('SQLite(sqlite2)')
except ImportError:
- logger.debug('no sqlite3 or pysqlite2.dbapi2 driver')
+ logger.debug('no SQLite drivers pysqlite2.dbapi2')
+
+ try:
+ from sqlite3 import dbapi2 as sqlite3
+ DRIVERS.append('SQLite(sqlite3)')
+ except ImportError:
+ logger.debug('no SQLite drivers sqlite3')
try:
# first try contrib driver, then from site-packages (if installed)
@@ -274,16 +282,23 @@ if not 'google' in drivers:
# end monkeypatch
except ImportError:
import pymysql
- drivers.append('pymysql')
+ DRIVERS.append('MySQL(pymysql)')
except ImportError:
- logger.debug('no pymysql driver')
+ logger.debug('no MySQL driver pymysql')
+
+ try:
+ import MySQLdb
+ DRIVERS.append('MySQL(MySQLdb)')
+ except ImportError:
+ logger.debug('no MySQL driver MySQLDB')
+
try:
import psycopg2
from psycopg2.extensions import adapt as psycopg2_adapt
- drivers.append('psycopg2')
+ DRIVERS.append('PostgreSQL(psycopg2)')
except ImportError:
- logger.debug('no psycopg2 driver')
+ logger.debug('no PostgreSQL driver psycopg2')
try:
# first try contrib driver, then from site-packages (if installed)
@@ -291,97 +306,108 @@ if not 'google' in drivers:
import contrib.pg8000.dbapi as pg8000
except ImportError:
import pg8000.dbapi as pg8000
- drivers.append('pg8000')
+ DRIVERS.append('PostgreSQL(pg8000)')
except ImportError:
- logger.debug('no pg8000 driver')
+ logger.debug('no PostgreSQL driver pg8000')
try:
import cx_Oracle
- drivers.append('Oracle')
+ DRIVERS.append('Oracle(cx_Oracle)')
except ImportError:
- logger.debug('no cx_Oracle driver')
+ logger.debug('no Oracle driver cx_Oracle')
try:
import pyodbc
- drivers.append('MSSQL/DB2/Teradata')
+ DRIVERS.append('MSSQL(pyodbc)')
+ DRIVERS.append('DB2(pyodbc)')
+ DRIVERS.append('Teradata(pyodbc)')
except ImportError:
- logger.debug('no MSSQL/DB2/Teradata driver')
+ logger.debug('no MSSQL/DB2/Teradata driver pyodbc')
try:
import Sybase
- drivers.append('Sybase')
+ DRIVERS.append('Sybase(Sybase)')
except ImportError:
logger.debug('no Sybase driver')
-
+
try:
import kinterbasdb
- drivers.append('Interbase')
+ DRIVERS.append('Interbase(kinterbasdb)')
+ DRIVERS.append('Firebird(kinterbasdb)')
except ImportError:
- logger.debug('no kinterbasdb driver')
+ logger.debug('no Firebird/Interbase driver kinterbasdb')
try:
- import firebirdsql
- drivers.append('Firebird')
+ import fdb
+ DRIVERS.append('Firbird(fdb)')
except ImportError:
- logger.debug('no Firebird driver')
+ logger.debug('no Firebird driver fdb')
+#####
+ try:
+ import firebirdsql
+ DRIVERS.append('Firebird(firebirdsql)')
+ except ImportError:
+ logger.debug('no Firebird driver firebirdsql')
try:
import informixdb
- drivers.append('Informix')
+ DRIVERS.append('Informix(informixdb)')
logger.warning('Informix support is experimental')
except ImportError:
- logger.debug('no informixdb driver')
+ logger.debug('no Informix driver informixdb')
try:
import sapdb
- drivers.append('SAPDB')
+ DRIVERS.append('SQL(sapdb)')
logger.warning('SAPDB support is experimental')
except ImportError:
- logger.debug('no sapdb driver')
+ logger.debug('no SAP driver sapdb')
try:
import cubriddb
- drivers.append('Cubrid')
+ DRIVERS.append('Cubrid(cubriddb)')
logger.warning('Cubrid support is experimental')
except ImportError:
- logger.debug('no cubriddb driver')
+ logger.debug('no Cubrid driver cubriddb')
try:
from com.ziclix.python.sql import zxJDBC
import java.sql
# Try sqlite jdbc driver from http://www.zentus.com/sqlitejdbc/
from org.sqlite import JDBC # required by java.sql; ensure we have it
- drivers.append('zxJDBC')
+ zxJDBC_sqlite = java.sql.DriverManager
+ DRIVERS.append('PostgreSQL(zxJDBC)')
+ DRIVERS.append('SQLite(zxJDBC)')
logger.warning('zxJDBC support is experimental')
is_jdbc = True
except ImportError:
- logger.debug('no zxJDBC driver')
+ logger.debug('no SQLite/PostgreSQL driver zxJDBC')
is_jdbc = False
try:
import ingresdbi
- drivers.append('Ingres')
+ DRIVERS.append('Ingres(ingresdbi)')
except ImportError:
- logger.debug('no Ingres driver')
+ logger.debug('no Ingres driver ingresdbi')
# NOTE could try JDBC.......
try:
import couchdb
- drivers.append('CouchDB')
+ DRIVERS.append('CouchDB(couchdb)')
except ImportError:
- logger.debug('no couchdb driver')
+ logger.debug('no Couchdb driver couchdb')
try:
import pymongo
- drivers.append('mongoDB')
+ DRIVERS.append('MongoDB(pymongo)')
except:
- logger.debug('no mongoDB driver')
+ logger.debug('no MongoDB driver pymongo')
try:
import imaplib
- drivers.append('IMAP')
+ DRIVERS.append('IMAP(imaplib)')
except:
- logger.debug('could not import imaplib')
+ logger.debug('no IMAP driver imaplib')
PLURALIZE_RULES = [
(re.compile('child$'), re.compile('child$'), 'children'),
@@ -414,9 +440,9 @@ def AND(a,b):
def IDENTITY(x): return x
def varquote_aux(name,quotestr='%s'):
- return name if regex_safe.match(name) else quotestr % name
+ return name if REGEX_W.match(name) else quotestr % name
-if 'google' in drivers:
+if 'google' in DRIVERS:
is_jdbc = False
@@ -563,8 +589,9 @@ class ConnectionPool(object):
###################################################################################
class BaseAdapter(ConnectionPool):
-
driver = None
+ driver_name = None
+ drivers = () # list of drivers from which to pick
maxcharlength = MAXCHARLENGTH
commit_on_alter_table = False
support_distributed_transaction = False
@@ -630,6 +657,30 @@ class BaseAdapter(ConnectionPool):
def file_delete(self, filename):
os.unlink(filename)
+ def find_driver(self,adapter_args,uri=None):
+ if hasattr(self,'driver') and self.driver!=None:
+ return
+ drivers_available = [driver for driver in self.drivers
+ if driver in globals()]
+ if uri:
+ items = uri.split('://',1)[0].split(':')
+ request_driver = items[1] if len(items)>1 else None
+ else:
+ request_driver = None
+ request_driver = request_driver or adapter_args.get('driver')
+ if request_driver:
+ if request_driver in drivers_available:
+ self.driver_name = request_driver
+ self.driver = globals().get(request_driver)
+ else:
+ raise RuntimeError, "driver %s not available" % request_driver
+ elif drivers_available:
+ self.driver_name = drivers_available[0]
+ self.driver = globals().get(self.driver_name)
+ else:
+ raise RuntimeError, "no driver available %s", self.drivers
+
+
def __init__(self, db,uri,pool_size=0, folder=None, db_codec='UTF-8',
credential_decoder=IDENTITY, driver_args={},
adapter_args={}):
@@ -659,6 +710,7 @@ class BaseAdapter(ConnectionPool):
migrate=True,
fake_migrate=False,
polymodel=None):
+ db = table._db
fields = []
# PostGIS geo fields are added after the table has been created
postcreation_fields = []
@@ -686,7 +738,7 @@ class BaseAdapter(ConnectionPool):
else:
if hasattr(table,'_primarykey'):
rtablename,rfieldname = referenced.split('.')
- rtable = table._db[rtablename]
+ rtable = db[rtablename]
rfield = rtable[rfieldname]
# must be PK reference or unique
if rfieldname in hasattr(rtable,'_primarykey') or \
@@ -708,8 +760,12 @@ class BaseAdapter(ConnectionPool):
on_delete_action=field.ondelete)
else:
# make a guess here for circular references
- id_fieldname = referenced in table._db \
- and table._db[referenced]._id.name or 'id'
+ if referenced in db:
+ id_fieldname = db[referenced]._id.name
+ elif referenced == tablename:
+ id_fieldname = table._id.name
+ else: #make a guess
+ id_fieldname = 'id'
ftype = types[field_type[:9]] % dict(
index_name = field_name+'__idx',
field_name = field_name,
@@ -796,7 +852,7 @@ class BaseAdapter(ConnectionPool):
fields = ',\n '.join(fields)
for rtablename in TFK:
rfields = TFK[rtablename]
- pkeys = table._db[rtablename]._primarykey
+ pkeys = db[rtablename]._primarykey
fkeys = [ rfields[k] for k in pkeys ]
fields = fields + ',\n ' + \
types['reference TFK'] % dict(
@@ -886,17 +942,21 @@ class BaseAdapter(ConnectionPool):
logfile,
fake_migrate=False,
):
- table._db._migrated.append(table._tablename)
+ db = table._db
+ db._migrated.append(table._tablename)
tablename = table._tablename
def fix(item):
k,v=item
if not isinstance(v,dict):
v=dict(type='unkown',sql=v)
return k.lower(),v
- ### make sure all field names are lower case to avoid conflicts
+ # make sure all field names are lower case to avoid
+ # migrations because of case cahnge
sql_fields = dict(map(fix,sql_fields.iteritems()))
sql_fields_old = dict(map(fix,sql_fields_old.iteritems()))
sql_fields_aux = dict(map(fix,sql_fields_aux.iteritems()))
+ if db._debug:
+ logging.debug('migrating %s to %s' % (sql_fields_old,sql_fields))
keys = sql_fields.keys()
for key in sql_fields_old:
@@ -941,10 +1001,11 @@ class BaseAdapter(ConnectionPool):
query = ['ALTER TABLE %s DROP %s;' % (tablename, key)]
metadata_change = True
elif sql_fields[key]['sql'] != sql_fields_old[key]['sql'] \
- and not isinstance(table[key].type, SQLCustomType) \
- and not table[key].type.startswith('reference')\
- and not table[key].type.startswith('double')\
- and not table[key].type.startswith('id'):
+ and not (key in table.fields and
+ isinstance(table[key].type, SQLCustomType)) \
+ and not sql_fields[key]['type'].startswith('reference')\
+ and not sql_fields[key]['type'].startswith('double')\
+ and not sql_fields[key]['type'].startswith('id'):
sql_fields_current[key] = sql_fields[key]
t = tablename
tt = sql_fields_aux[key]['sql'].replace(', ', new_add)
@@ -970,7 +1031,7 @@ class BaseAdapter(ConnectionPool):
if query:
logfile.write('timestamp: %s\n'
% datetime.datetime.today().isoformat())
- table._db['_lastsql'] = '\n'.join(query)
+ db['_lastsql'] = '\n'.join(query)
for sub_query in query:
logfile.write(sub_query + '\n')
if not fake_migrate:
@@ -978,8 +1039,8 @@ class BaseAdapter(ConnectionPool):
# Caveat: mysql, oracle and firebird do not allow multiple alter table
# in one transaction so we must commit partial transactions and
# update table._dbt after alter table.
- if table._db._adapter.commit_on_alter_table:
- table._db.commit()
+ if db._adapter.commit_on_alter_table:
+ db.commit()
tfile = self.file_open(table._dbt, 'w')
cPickle.dump(sql_fields_current, tfile)
self.file_close(tfile)
@@ -993,7 +1054,7 @@ class BaseAdapter(ConnectionPool):
if metadata_change and \
not (query and self.dbengine in ('mysql','oracle','firebird')):
- table._db.commit()
+ db.commit()
tfile = self.file_open(table._dbt, 'w')
cPickle.dump(sql_fields_current, tfile)
self.file_close(tfile)
@@ -1342,7 +1403,7 @@ class BaseAdapter(ConnectionPool):
if isinstance(item,SQLALL):
new_fields += item._table
elif isinstance(item,str):
- if regex_table_field.match(item):
+ if REGEX_TABLE_DOT_FIELD.match(item):
tablename,fieldname = item.split('.')
append(db[tablename][fieldname])
else:
@@ -1364,7 +1425,7 @@ class BaseAdapter(ConnectionPool):
tablenames = tables(query)
for field in fields:
if isinstance(field, basestring) \
- and regex_table_field.match(field):
+ and REGEX_TABLE_DOT_FIELD.match(field):
tn,fn = field.split('.')
field = self.db[tn][fn]
for tablename in tables(field):
@@ -1707,7 +1768,7 @@ class BaseAdapter(ConnectionPool):
elif field_type == 'blob' and not blob_decode:
return value
else:
- key = regex_type.match(field_type).group(0)
+ key = REGEX_TYPE.match(field_type).group(0)
return self.parsemap[key](value,field_type)
def parse_reference(self, value, field_type):
@@ -1738,18 +1799,18 @@ class BaseAdapter(ConnectionPool):
def parse_datetime(self, value, field_type):
if not isinstance(value, datetime.datetime):
- if '+' in value:
- value,tz = value.split('+')
+ value = str(value)
+ date_part,time_part,timezone = value[:10],value[11:19],value[19:]
+ if '+' in timezone:
+ ms,tz = timezone.split('+')
h,m = tz.split(':')
dt = datetime.timedelta(seconds=3600*int(h)+60*int(m))
- elif '-' in value:
- value,tz = value.split('-')
+ elif '-' in timezone:
+ ms,tz = timezone.split('-')
h,m = tz.split(':')
dt = -datetime.timedelta(seconds=3600*int(h)+60*int(m))
else:
dt = None
- date_part, time_part = (
- str(value).replace('T',' ')+' ').split(' ',1)
(y, m, d) = map(int,date_part.split('-'))
time_parts = time_part and time_part.split(':')[:3] or (0,0,0)
while len(time_parts)<3: time_parts.append(0)
@@ -1822,7 +1883,7 @@ class BaseAdapter(ConnectionPool):
new_rows = []
tmps = []
for colname in colnames:
- if not regex_table_field.match(colname):
+ if not REGEX_TABLE_DOT_FIELD.match(colname):
tmps.append(None)
else:
(tablename, fieldname) = colname.split('.')
@@ -1878,7 +1939,7 @@ class BaseAdapter(ConnectionPool):
self.parse_value(value,
fields[j].type,blob_decode)
new_column_name = \
- regex_select_as_parser.search(colname)
+ REGEX_SELECT_AS_PARSER.search(colname)
if not new_column_name is None:
column_name = new_column_name.groups(0)
setattr(new_row,column_name[0],value)
@@ -1936,8 +1997,8 @@ class BaseAdapter(ConnectionPool):
###################################################################################
class SQLiteAdapter(BaseAdapter):
+ drivers = ('sqlite3','sqlite2')
- driver = globals().get('sqlite3', None)
can_select_for_update = None # support ourselves with BEGIN TRANSACTION
def EXTRACT(self,field,what):
@@ -1966,11 +2027,10 @@ class SQLiteAdapter(BaseAdapter):
def __init__(self, db, uri, pool_size=0, folder=None, db_codec ='UTF-8',
credential_decoder=IDENTITY, driver_args={},
adapter_args={}):
- if not self.driver:
- raise RuntimeError, "Unable to import driver"
self.db = db
self.dbengine = "sqlite"
self.uri = uri
+ self.find_driver(adapter_args)
self.pool_size = 0
self.folder = folder
self.db_codec = db_codec
@@ -1980,7 +2040,7 @@ class SQLiteAdapter(BaseAdapter):
if uri.startswith('sqlite:memory'):
dbpath = ':memory:'
else:
- dbpath = uri.split('://')[1]
+ dbpath = uri.split('://',1)[1]
if dbpath[0] != '/':
dbpath = pjoin(
self.folder.decode(path_encoding).encode('utf8'), dbpath)
@@ -2022,19 +2082,18 @@ class SQLiteAdapter(BaseAdapter):
return super(SQLiteAdapter, self).select(query, fields, attributes)
class SpatiaLiteAdapter(SQLiteAdapter):
+ drivers = ('sqlite3','sqlite2')
- import copy
types = copy.copy(BaseAdapter.types)
types.update(geometry='GEOMETRY')
def __init__(self, db, uri, pool_size=0, folder=None, db_codec ='UTF-8',
credential_decoder=IDENTITY, driver_args={},
adapter_args={}, srid=4326):
- if not self.driver:
- raise RuntimeError, "Unable to import driver"
self.db = db
self.dbengine = "spatialite"
self.uri = uri
+ self.find_driver(adapter_args)
self.pool_size = 0
self.folder = folder
self.db_codec = db_codec
@@ -2045,7 +2104,7 @@ class SpatiaLiteAdapter(SQLiteAdapter):
if uri.startswith('spatialite:memory'):
dbpath = ':memory:'
else:
- dbpath = uri.split('://')[1]
+ dbpath = uri.split('://',1)[1]
if dbpath[0] != '/':
dbpath = pjoin(
self.folder.decode(path_encoding).encode('utf8'), dbpath)
@@ -2131,17 +2190,15 @@ class SpatiaLiteAdapter(SQLiteAdapter):
class JDBCSQLiteAdapter(SQLiteAdapter):
-
- driver = globals().get('zxJDBC', None)
+ drivers = ('zxJDBC_sqlite',)
def __init__(self, db, uri, pool_size=0, folder=None, db_codec='UTF-8',
credential_decoder=IDENTITY, driver_args={},
adapter_args={}):
- if not self.driver:
- raise RuntimeError, "Unable to import driver"
self.db = db
self.dbengine = "sqlite"
self.uri = uri
+ self.find_driver(adapter_args)
self.pool_size = pool_size
self.folder = folder
self.db_codec = db_codec
@@ -2151,13 +2208,13 @@ class JDBCSQLiteAdapter(SQLiteAdapter):
if uri.startswith('sqlite:memory'):
dbpath = ':memory:'
else:
- dbpath = uri.split('://')[1]
+ dbpath = uri.split('://',1)[1]
if dbpath[0] != '/':
dbpath = pjoin(
self.folder.decode(path_encoding).encode('utf8'), dbpath)
def connect(dbpath=dbpath,driver_args=driver_args):
return self.driver.connect(
- java.sql.DriverManager.getConnection('jdbc:sqlite:'+dbpath),
+ self.driver.getConnection('jdbc:sqlite:'+dbpath),
**driver_args)
self.pool_connection(connect)
self.after_connection()
@@ -2172,8 +2229,8 @@ class JDBCSQLiteAdapter(SQLiteAdapter):
class MySQLAdapter(BaseAdapter):
+ drivers = ('MySQLdb','pymysql')
- driver = globals().get('pymysql',None)
maxcharlength = 255
commit_on_alter_table = True
support_distributed_transaction = True
@@ -2198,7 +2255,7 @@ class MySQLAdapter(BaseAdapter):
'list:string': 'LONGTEXT',
'list:reference': 'LONGTEXT',
'big-id': 'BIGINT AUTO_INCREMENT NOT NULL',
- 'big-reference': 'BIGINT, INDEX %(index_name`)s (%(field_name)s), FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s',
+ 'big-reference': 'BIGINT, INDEX %(index_name)s (%(field_name)s), FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s',
}
def varquote(self,name):
@@ -2232,20 +2289,21 @@ class MySQLAdapter(BaseAdapter):
def concat_add(self,table):
return '; ALTER TABLE %s ADD ' % table
+ REGEX_URI = re.compile('^(?P[^:@]+)(\:(?P[^@]*))?@(?P[^\:/]+)(\:(?P[0-9]+))?/(?P[^?]+)(\?set_encoding=(?P\w+))?$')
+
def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8',
credential_decoder=IDENTITY, driver_args={},
adapter_args={}):
- if not self.driver:
- raise RuntimeError, "Unable to import driver"
self.db = db
self.dbengine = "mysql"
self.uri = uri
+ self.find_driver(adapter_args,uri)
self.pool_size = pool_size
self.folder = folder
self.db_codec = db_codec
self.find_or_make_work_folder()
- uri = uri.split('://')[1]
- m = re.compile('^(?P[^:@]+)(\:(?P[^@]*))?@(?P[^\:/]+)(\:(?P[0-9]+))?/(?P[^?]+)(\?set_encoding=(?P\w+))?$').match(uri)
+ ruri = uri.split('://',1)[1]
+ m = self.REGEX_URI.match(ruri)
if not m:
raise SyntaxError, \
"Invalid URI string in DAL: %s" % self.uri
@@ -2269,6 +2327,8 @@ class MySQLAdapter(BaseAdapter):
host=host,
port=port,
charset=charset)
+
+
def connect(driver_args=driver_args):
return self.driver.connect(**driver_args)
self.pool_connection(connect)
@@ -2283,10 +2343,7 @@ class MySQLAdapter(BaseAdapter):
return int(self.cursor.fetchone()[0])
class PostgreSQLAdapter(BaseAdapter):
-
- driver = None
- drivers = {'psycopg2': globals().get('psycopg2', None),
- 'pg8000': globals().get('pg8000', None), }
+ drivers = ('psycopg2','pg8000')
support_distributed_transaction = True
types = {
@@ -2319,9 +2376,12 @@ class PostgreSQLAdapter(BaseAdapter):
return varquote_aux(name,'"%s"')
def adapt(self,obj):
- #if self.driver == self.drivers.get('pg8000'):
- # obj = str(obj).replace('%','%%')
- return psycopg2_adapt(obj).getquoted()
+ if self.driver_name == 'psycopg2':
+ return psycopg2_adapt(obj).getquoted()
+ elif self.driver_name == 'pg8000':
+ return str(obj).replace("%","%%").replace("'","''")
+ else:
+ return str(obj).replace("'","''")
def sequence_name(self,table):
return '%s_id_Seq' % table
@@ -2355,21 +2415,22 @@ class PostgreSQLAdapter(BaseAdapter):
# % (table._tablename, table._fieldname, table._sequence_name))
self.execute(query)
+ REGEX_URI = re.compile('^(?P[^:@]+)(\:(?P[^@]*))?@(?P[^\:@]+)(\:(?P[0-9]+))?/(?P[^\?]+)(\?sslmode=(?P.+))?$')
+
def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8',
credential_decoder=IDENTITY, driver_args={},
adapter_args={}, srid=4326):
- if not self.drivers.get('psycopg2') and not self.drivers.get('pg8000'):
- raise RuntimeError, "Unable to import any drivers (psycopg2 or pg8000)"
self.db = db
self.dbengine = "postgres"
self.uri = uri
+ self.find_driver(adapter_args,uri)
self.pool_size = pool_size
self.folder = folder
self.db_codec = db_codec
self.srid = srid
self.find_or_make_work_folder()
- library, uri = uri.split('://')[:2]
- m = re.compile('^(?P[^:@]+)(\:(?P[^@]*))?@(?P[^\:@]+)(\:(?P[0-9]+))?/(?P[^\?]+)(\?sslmode=(?P.+))?$').match(uri)
+ ruri = uri.split('://',1)[1]
+ m = self.REGEX_URI.match(ruri)
if not m:
raise SyntaxError, "Invalid URI string in DAL"
user = credential_decoder(m.group('user'))
@@ -2395,20 +2456,6 @@ class PostgreSQLAdapter(BaseAdapter):
"port=%s password='%s'") \
% (db, user, host, port, password)
# choose diver according uri
- if library == "postgres":
- if 'psycopg2' in self.drivers:
- self.driver = self.drivers['psycopg2']
- elif 'pg8000' in self.drivers:
- self.driver = self.drivers['pg8000']
- else:
- raise RuntimeError, "No pgsql driver"
- elif library == "postgres:psycopg2":
- self.driver = self.drivers.get('psycopg2')
- elif library == "postgres:pg8000":
- self.driver = self.drivers.get('pg8000')
- if not self.driver:
- raise RuntimeError, "%s is not available" % library
-
self.__version__ = "%s %s" % (self.driver.__name__, self.driver.__version__)
def connect(msg=msg,driver_args=driver_args):
return self.driver.connect(msg,**driver_args)
@@ -2539,6 +2586,8 @@ class PostgreSQLAdapter(BaseAdapter):
return BaseAdapter.represent(self, obj, fieldtype)
class NewPostgreSQLAdapter(PostgreSQLAdapter):
+ drivers = ('psycopg2','pg8000')
+
types = {
'boolean': 'CHAR(1)',
'string': 'VARCHAR(%(length)s)',
@@ -2590,21 +2639,23 @@ class NewPostgreSQLAdapter(PostgreSQLAdapter):
class JDBCPostgreSQLAdapter(PostgreSQLAdapter):
+ drivers = ('zxJDBC',)
+
+ REGEX_URI = re.compile('^(?P[^:@]+)(\:(?P[^@]*))?@(?P[^\:/]+)(\:(?P[0-9]+))?/(?P.+)$')
def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8',
credential_decoder=IDENTITY, driver_args={},
adapter_args={}):
- if not self.driver:
- raise RuntimeError, "Unable to import driver"
self.db = db
self.dbengine = "postgres"
self.uri = uri
+ self.find_driver(adapter_args,uri)
self.pool_size = pool_size
self.folder = folder
self.db_codec = db_codec
self.find_or_make_work_folder()
- uri = uri.split('://')[1]
- m = re.compile('^(?P[^:@]+)(\:(?P[^@]*))?@(?P[^\:/]+)(\:(?P[0-9]+))?/(?P.+)$').match(uri)
+ ruri = uri.split('://',1)[1]
+ m = self.REGEX_URI.match(ruri)
if not m:
raise SyntaxError, "Invalid URI string in DAL"
user = credential_decoder(m.group('user'))
@@ -2633,8 +2684,7 @@ class JDBCPostgreSQLAdapter(PostgreSQLAdapter):
class OracleAdapter(BaseAdapter):
-
- driver = globals().get('cx_Oracle',None)
+ drivers = ('cx_Oracle',)
commit_on_alter_table = False
types = {
@@ -2721,19 +2771,18 @@ class OracleAdapter(BaseAdapter):
def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8',
credential_decoder=IDENTITY, driver_args={},
adapter_args={}):
- if not self.driver:
- raise RuntimeError, "Unable to import driver"
self.db = db
self.dbengine = "oracle"
self.uri = uri
+ self.find_driver(adapter_args,uri)
self.pool_size = pool_size
self.folder = folder
self.db_codec = db_codec
self.find_or_make_work_folder()
- uri = uri.split('://')[1]
+ ruri = uri.split('://',1)[1]
if not 'threaded' in driver_args:
driver_args['threaded']=True
- def connect(uri=uri,driver_args=driver_args):
+ def connect(uri=ruri,driver_args=driver_args):
return self.driver.connect(uri,**driver_args)
self.pool_connection(connect)
self.after_connection()
@@ -2741,6 +2790,7 @@ class OracleAdapter(BaseAdapter):
def after_connection(self):
self.execute("ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD HH24:MI:SS';")
self.execute("ALTER SESSION SET NLS_TIMESTAMP_FORMAT = 'YYYY-MM-DD HH24:MI:SS';")
+
oracle_fix = re.compile("[^']*('[^']*'[^']*)*\:(?PCLOB\('([^']+|'')*'\))")
def execute(self, command, args=None):
@@ -2792,14 +2842,13 @@ class OracleAdapter(BaseAdapter):
if blob_decode and isinstance(value, cx_Oracle.LOB):
try:
value = value.read()
- except cx_Oracle.ProgrammingError:
+ except self.driver.ProgrammingError:
# After a subsequent fetch the LOB value is not valid anymore
pass
return BaseAdapter.parse_value(self, value, field_type, blob_decode)
class MSSQLAdapter(BaseAdapter):
-
- driver = globals().get('pyodbc',None)
+ drivers = ('pyodbc',)
types = {
'boolean': 'BIT',
@@ -2869,24 +2918,27 @@ class MSSQLAdapter(BaseAdapter):
return '0'
return None
+ REGEX_DSN = re.compile('^(?P.+)$')
+ REGEX_URI = re.compile('^(?P[^:@]+)(\:(?P[^@]*))?@(?P[^\:/]+)(\:(?P[0-9]+))?/(?P[^\?]+)(\?(?P.*))?$')
+ REGEX_ARGPATTERN = re.compile('(?P[^=]+)=(?P[^&]*)')
+
def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8',
credential_decoder=IDENTITY, driver_args={},
adapter_args={}, fake_connect=False, srid=4326):
- if not self.driver:
- raise RuntimeError, "Unable to import driver"
self.db = db
self.dbengine = "mssql"
self.uri = uri
+ self.find_driver(adapter_args,uri)
self.pool_size = pool_size
self.folder = folder
self.db_codec = db_codec
self.srid = srid
self.find_or_make_work_folder()
# ## read: http://bytes.com/groups/python/460325-cx_oracle-utf8
- uri = uri.split('://')[1]
- if '@' not in uri:
+ ruri = uri.split('://',1)[1]
+ if '@' not in ruri:
try:
- m = re.compile('^(?P.+)$').match(uri)
+ m = self.REGEX_DSN.match(ruri)
if not m:
raise SyntaxError, \
'Parsing uri string(%s) has no result' % self.uri
@@ -2899,10 +2951,10 @@ class MSSQLAdapter(BaseAdapter):
# was cnxn = 'DSN=%s' % dsn
cnxn = dsn
else:
- m = re.compile('^(?P[^:@]+)(\:(?P[^@]*))?@(?P[^\:/]+)(\:(?P[0-9]+))?/(?P[^\?]+)(\?(?P.*))?$').match(uri)
+ m = self.REGEX_URI.match(ruri)
if not m:
raise SyntaxError, \
- "Invalid URI string in DAL: %s" % uri
+ "Invalid URI string in DAL: %s" % self.uri
user = credential_decoder(m.group('user'))
if not user:
raise SyntaxError, 'User required'
@@ -2920,9 +2972,8 @@ class MSSQLAdapter(BaseAdapter):
# (in the form of arg1=value1&arg2=value2&...)
# Default values (drivers like FreeTDS insist on uppercase parameter keys)
argsdict = { 'DRIVER':'{SQL Server}' }
- urlargs = m.group('urlargs') or ''
- argpattern = re.compile('(?P[^=]+)=(?P[^&]*)')
- for argmatch in argpattern.finditer(urlargs):
+ urlargs = m.group('urlargs') or ''
+ for argmatch in self.REGEX_ARGPATTERN.finditer(urlargs):
argsdict[str(argmatch.group('argkey')).upper()] = argmatch.group('argvalue')
urlargs = ';'.join(['%s=%s' % (ak, av) for (ak, av) in argsdict.iteritems()])
cnxn = 'SERVER=%s;PORT=%s;DATABASE=%s;UID=%s;PWD=%s;%s' \
@@ -2997,6 +3048,8 @@ class MSSQLAdapter(BaseAdapter):
class MSSQL2Adapter(MSSQLAdapter):
+ drivers = ('pyodbc',)
+
types = {
'boolean': 'CHAR(1)',
'string': 'NVARCHAR(%(length)s)',
@@ -3033,8 +3086,7 @@ class MSSQL2Adapter(MSSQLAdapter):
return self.log_execute(a.decode('utf8'))
class SybaseAdapter(MSSQLAdapter):
-
- driver = globals().get('Sybase',None)
+ drivers = ('Sybase',)
types = {
'boolean': 'BIT',
@@ -3068,22 +3120,20 @@ class SybaseAdapter(MSSQLAdapter):
def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8',
credential_decoder=IDENTITY, driver_args={},
adapter_args={}, fake_connect=False, srid=4326):
- ### Fix this for sybase
- if not self.driver:
- raise RuntimeError, "Unable to import driver"
self.db = db
self.dbengine = "sybase"
self.uri = uri
+ self.find_driver(adapter_args,uri)
self.pool_size = pool_size
self.folder = folder
self.db_codec = db_codec
self.srid = srid
self.find_or_make_work_folder()
# ## read: http://bytes.com/groups/python/460325-cx_oracle-utf8
- uri = uri.split('://')[1]
- if '@' not in uri:
+ ruri = uri.split('://',1)[1]
+ if '@' not in ruri:
try:
- m = re.compile('^(?P.+)$').match(uri)
+ m = self.REGEX_DSN.match(ruri)
if not m:
raise SyntaxError, \
'Parsing uri string(%s) has no result' % self.uri
@@ -3094,10 +3144,10 @@ class SybaseAdapter(MSSQLAdapter):
logger.error('NdGpatch error')
raise e
else:
- m = re.compile('^(?P[^:@]+)(\:(?P[^@]*))?@(?P[^\:/]+)(\:(?P[0-9]+))?/(?P[^\?]+)(\?(?P.*))?$').match(uri)
+ m = self.REGEX_URI.match(uri)
if not m:
raise SyntaxError, \
- "Invalid URI string in DAL: %s" % uri
+ "Invalid URI string in DAL: %s" % self.uri
user = credential_decoder(m.group('user'))
if not user:
raise SyntaxError, 'User required'
@@ -3129,8 +3179,7 @@ class SybaseAdapter(MSSQLAdapter):
class FireBirdAdapter(BaseAdapter):
-
- driver = globals().get('pyodbc',None)
+ drivers = ('kinterbasdb','firebirdsql','fdb','pyodbc')
commit_on_alter_table = False
support_distributed_transaction = True
@@ -3187,30 +3236,23 @@ class FireBirdAdapter(BaseAdapter):
return ['DELETE FROM %s;' % table._tablename,
'SET GENERATOR %s TO 0;' % table._sequence_name]
+ REGEX_URI = re.compile('^(?P[^:@]+)(\:(?P[^@]*))?@(?P[^\:/]+)(\:(?P[0-9]+))?/(?P.+?)(\?set_encoding=(?P\w+))?$')
+
def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8',
credential_decoder=IDENTITY, driver_args={},
- adapter_args={}):
- if 'driver_name' in adapter_args:
- if adapter_args['driver_name'] == 'kinterbasdb':
- self.driver = kinterbasdb
- elif adapter_args['driver_name'] == 'firebirdsql':
- self.driver = firebirdsql
- else:
- self.driver = kinterbasdb
-
- if not self.driver:
- raise RuntimeError, "Unable to import driver"
+ adapter_args={}):
self.db = db
self.dbengine = "firebird"
self.uri = uri
+ self.find_driver(adapter_args,uri)
self.pool_size = pool_size
self.folder = folder
self.db_codec = db_codec
self.find_or_make_work_folder()
- uri = uri.split('://')[1]
- m = re.compile('^(?P[^:@]+)(\:(?P[^@]*))?@(?P[^\:/]+)(\:(?P[0-9]+))?/(?P.+?)(\?set_encoding=(?P\w+))?$').match(uri)
+ ruri = uri.split('://',1)[1]
+ m = self.REGEX_URI.match(ruri)
if not m:
- raise SyntaxError, "Invalid URI string in DAL: %s" % uri
+ raise SyntaxError, "Invalid URI string in DAL: %s" % self.uri
user = credential_decoder(m.group('user'))
if not user:
raise SyntaxError, 'User required'
@@ -3251,30 +3293,23 @@ class FireBirdAdapter(BaseAdapter):
class FireBirdEmbeddedAdapter(FireBirdAdapter):
+ drivers = ('kinterbasdb','firebirdsql','fdb','pyodbc')
+
+ REGEX_URI = re.compile('^(?P[^:@]+)(\:(?P[^@]*))?@(?P[^\?]+)(\?set_encoding=(?P\w+))?$')
def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8',
credential_decoder=IDENTITY, driver_args={},
adapter_args={}):
-
- if 'driver_name' in adapter_args:
- if adapter_args['driver_name'] == 'kinterbasdb':
- self.driver = kinterbasdb
- elif adapter_args['driver_name'] == 'firebirdsql':
- self.driver = firebirdsql
- else:
- self.driver = kinterbasdb
-
- if not self.driver:
- raise RuntimeError, "Unable to import driver"
self.db = db
self.dbengine = "firebird"
self.uri = uri
+ self.find_driver(adapter_args,uri)
self.pool_size = pool_size
self.folder = folder
self.db_codec = db_codec
self.find_or_make_work_folder()
- uri = uri.split('://')[1]
- m = re.compile('^(?P[^:@]+)(\:(?P[^@]*))?@(?P[^\?]+)(\?set_encoding=(?P\w+))?$').match(uri)
+ ruri = uri.split('://',1)[1]
+ m = self.REGEX_URI.match(ruri)
if not m:
raise SyntaxError, \
"Invalid URI string in DAL: %s" % self.uri
@@ -3296,9 +3331,7 @@ class FireBirdEmbeddedAdapter(FireBirdAdapter):
user=credential_decoder(user),
password=credential_decoder(password),
charset=charset)
- #def connect(driver_args=driver_args):
- # return kinterbasdb.connect(**driver_args)
-
+
def connect(driver_args=driver_args):
return self.driver.connect(**driver_args)
self.pool_connection(connect)
@@ -3306,8 +3339,7 @@ class FireBirdEmbeddedAdapter(FireBirdAdapter):
class InformixAdapter(BaseAdapter):
-
- driver = globals().get('informixdb',None)
+ drivers = ('informixdb',)
types = {
'boolean': 'CHAR(1)',
@@ -3371,20 +3403,21 @@ class InformixAdapter(BaseAdapter):
return "to_date('%s','%%Y-%%m-%%d %%H:%%M:%%S')" % obj
return None
+ REGEX_URI = re.compile('^(?P[^:@]+)(\:(?P[^@]*))?@(?P[^\:/]+)(\:(?P[0-9]+))?/(?P.+)$')
+
def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8',
credential_decoder=IDENTITY, driver_args={},
adapter_args={}):
- if not self.driver:
- raise RuntimeError, "Unable to import driver"
self.db = db
self.dbengine = "informix"
self.uri = uri
+ self.find_driver(adapter_args,uri)
self.pool_size = pool_size
self.folder = folder
self.db_codec = db_codec
self.find_or_make_work_folder()
- uri = uri.split('://')[1]
- m = re.compile('^(?P[^:@]+)(\:(?P[^@]*))?@(?P[^\:/]+)(\:(?P[0-9]+))?/(?P.+)$').match(uri)
+ ruri = uri.split('://',1)[1]
+ m = self.REGEX_URI.match(ruri)
if not m:
raise SyntaxError, \
"Invalid URI string in DAL: %s" % self.uri
@@ -3422,8 +3455,7 @@ class InformixAdapter(BaseAdapter):
class DB2Adapter(BaseAdapter):
-
- driver = globals().get('pyodbc',None)
+ drivers = ('pyodbc',)
types = {
'boolean': 'CHAR(1)',
@@ -3478,17 +3510,16 @@ class DB2Adapter(BaseAdapter):
def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8',
credential_decoder=IDENTITY, driver_args={},
adapter_args={}):
- if not self.driver:
- raise RuntimeError, "Unable to import driver"
self.db = db
self.dbengine = "db2"
self.uri = uri
+ self.find_driver(adapter_args,uri)
self.pool_size = pool_size
self.folder = folder
self.db_codec = db_codec
self.find_or_make_work_folder()
- cnxn = uri.split('://', 1)[1]
- def connect(cnxn=cnxn,driver_args=driver_args):
+ ruri = uri.split('://', 1)[1]
+ def connect(cnxn=ruri,driver_args=driver_args):
return self.driver.connect(cnxn,**driver_args)
self.pool_connection(connect)
self.after_connection()
@@ -3509,8 +3540,7 @@ class DB2Adapter(BaseAdapter):
class TeradataAdapter(BaseAdapter):
-
- driver = globals().get('pyodbc',None)
+ drivers = ('pyodbc',)
types = {
'boolean': 'CHAR(1)',
@@ -3543,17 +3573,16 @@ class TeradataAdapter(BaseAdapter):
def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8',
credential_decoder=IDENTITY, driver_args={},
adapter_args={}):
- if not self.driver:
- raise RuntimeError, "Unable to import driver"
self.db = db
self.dbengine = "teradata"
self.uri = uri
+ self.find_driver(adapter_args,uri)
self.pool_size = pool_size
self.folder = folder
self.db_codec = db_codec
self.find_or_make_work_folder()
- cnxn = uri.split('://', 1)[1]
- def connect(cnxn=cnxn,driver_args=driver_args):
+ ruri = uri.split('://', 1)[1]
+ def connect(cnxn=ruri,driver_args=driver_args):
return self.driver.connect(cnxn,**driver_args)
self.pool_connection(connect)
self.after_connection()
@@ -3577,8 +3606,7 @@ INGRES_SEQNAME='ii***lineitemsequence' # NOTE invalid database object name
# to be a delimited identifier)
class IngresAdapter(BaseAdapter):
-
- driver = globals().get('ingresdbi',None)
+ drivers = ('ingresdbi',)
types = {
'boolean': 'CHAR(1)',
@@ -3626,11 +3654,10 @@ class IngresAdapter(BaseAdapter):
def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8',
credential_decoder=IDENTITY, driver_args={},
adapter_args={}):
- if not self.driver:
- raise RuntimeError, "Unable to import driver"
self.db = db
self.dbengine = "ingres"
self.uri = uri
+ self.find_driver(adapter_args,uri)
self.pool_size = pool_size
self.folder = folder
self.db_codec = db_codec
@@ -3680,6 +3707,9 @@ class IngresAdapter(BaseAdapter):
class IngresUnicodeAdapter(IngresAdapter):
+
+ drivers = ('ingresdbi',)
+
types = {
'boolean': 'CHAR(1)',
'string': 'NVARCHAR(%(length)s)',
@@ -3707,8 +3737,8 @@ class IngresUnicodeAdapter(IngresAdapter):
}
class SAPDBAdapter(BaseAdapter):
+ drivers = ('sapdb',)
- driver = globals().get('sapdb',None)
support_distributed_transaction = False
types = {
'boolean': 'CHAR(1)',
@@ -3754,20 +3784,22 @@ class SAPDBAdapter(BaseAdapter):
% (table._tablename, table._id.name, table._sequence_name))
self.execute(query)
+ REGEX_URI = re.compile('^(?P[^:@]+)(\:(?P[^@]*))?@(?P[^\:@]+)(\:(?P[0-9]+))?/(?P[^\?]+)(\?sslmode=(?P.+))?$')
+
+
def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8',
credential_decoder=IDENTITY, driver_args={},
adapter_args={}):
- if not self.driver:
- raise RuntimeError, "Unable to import driver"
self.db = db
self.dbengine = "sapdb"
self.uri = uri
+ self.find_driver(adapter_args,uri)
self.pool_size = pool_size
self.folder = folder
self.db_codec = db_codec
self.find_or_make_work_folder()
- uri = uri.split('://')[1]
- m = re.compile('^(?P[^:@]+)(\:(?P[^@]*))?@(?P[^\:@]+)(\:(?P[0-9]+))?/(?P[^\?]+)(\?sslmode=(?P.+))?$').match(uri)
+ ruri = uri.split('://',1)[1]
+ m = self.REGEX_URI.match(ruri)
if not m:
raise SyntaxError, "Invalid URI string in DAL"
user = credential_decoder(m.group('user'))
@@ -3794,23 +3826,23 @@ class SAPDBAdapter(BaseAdapter):
return int(self.cursor.fetchone()[0])
class CubridAdapter(MySQLAdapter):
+ drivers = ('cubriddb',)
- driver = globals().get('cubriddb', None)
+ REGEX_URI = re.compile('^(?P[^:@]+)(\:(?P[^@]*))?@(?P[^\:/]+)(\:(?P[0-9]+))?/(?P[^?]+)(\?set_encoding=(?P\w+))?$')
def __init__(self, db, uri, pool_size=0, folder=None, db_codec='UTF-8',
credential_decoder=IDENTITY, driver_args={},
adapter_args={}):
- if not self.driver:
- raise RuntimeError, "Unable to import driver"
self.db = db
self.dbengine = "cubrid"
self.uri = uri
+ self.find_driver(adapter_args,uri)
self.pool_size = pool_size
self.folder = folder
self.db_codec = db_codec
self.find_or_make_work_folder()
- uri = uri.split('://')[1]
- m = re.compile('^(?P[^:@]+)(\:(?P[^@]*))?@(?P[^\:/]+)(\:(?P[0-9]+))?/(?P[^?]+)(\?set_encoding=(?P\w+))?$').match(uri)
+ ruri = uri.split('://',1)[1]
+ m = self.REGEX_URI.match(ruri)
if not m:
raise SyntaxError, \
"Invalid URI string in DAL: %s" % self.uri
@@ -3929,6 +3961,8 @@ class UseDatabaseStoredFile:
class GoogleSQLAdapter(UseDatabaseStoredFile,MySQLAdapter):
uploads_in_blob = True
+ REGEX_URI = re.compile('^(?P.*)/(?P.*)$')
+
def __init__(self, db, uri='google:sql://realm:domain/database',
pool_size=0, folder=None, db_codec='UTF-8',
credential_decoder=IDENTITY, driver_args={},
@@ -3941,10 +3975,10 @@ class GoogleSQLAdapter(UseDatabaseStoredFile,MySQLAdapter):
self.db_codec = db_codec
self.folder = folder or pjoin('$HOME',thread.folder.split(
os.sep+'applications'+os.sep,1)[1])
-
- m = re.compile('^(?P.*)/(?P.*)$').match(self.uri[len('google:sql://'):])
+ ruri = uri.split("://")[1]
+ m = self.REGEX_URI.match(ruri)
if not m:
- raise SyntaxError, "Invalid URI string in SQLDB: %s" % self._uri
+ raise SyntaxError, "Invalid URI string in SQLDB: %s" % self.uri
instance = credential_decoder(m.group('instance'))
self.dbstring = db = credential_decoder(m.group('db'))
driver_args['instance'] = instance
@@ -4138,6 +4172,8 @@ class GoogleDatastoreAdapter(NoSQLAdapter):
def file_open(self, filename, mode='rb', lock=True): pass
def file_close(self, fileobj): pass
+ REGEX_NAMESPACE = re.compile('.*://(?P.+)')
+
def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8',
credential_decoder=IDENTITY, driver_args={},
adapter_args={}):
@@ -4169,7 +4205,7 @@ class GoogleDatastoreAdapter(NoSQLAdapter):
db['_lastsql'] = ''
self.db_codec = 'UTF-8'
self.pool_size = 0
- match = re.compile('.*://(?P.+)').match(uri)
+ match = self.REGEX_NAMESPACE.match(uri)
if match:
namespace_manager.set_namespace(match.group('namespace'))
@@ -4329,9 +4365,9 @@ class GoogleDatastoreAdapter(NoSQLAdapter):
def select_raw(self,query,fields=None,attributes=None):
db = self.db
- args_get = attributes.get
fields = fields or []
attributes = attributes or {}
+ args_get = attributes.get
new_fields = []
for item in fields:
if isinstance(item,SQLALL):
@@ -4533,6 +4569,8 @@ def int2uuid(n):
return str(uuid.UUID(int=n))
class CouchDBAdapter(NoSQLAdapter):
+ drivers = ('couchdb',)
+
uploads_in_blob = True
types = {
'boolean': bool,
@@ -4599,6 +4637,7 @@ class CouchDBAdapter(NoSQLAdapter):
adapter_args={}):
self.db = db
self.uri = uri
+ self.find_driver(adapter_args)
self.dbengine = 'couchdb'
self.folder = folder
db['_lastsql'] = ''
@@ -4607,7 +4646,7 @@ class CouchDBAdapter(NoSQLAdapter):
url='http://'+uri[10:]
def connect(url=url,driver_args=driver_args):
- return couchdb.Server(url,**driver_args)
+ return self.driver.Server(url,**driver_args)
self.pool_connection(connect,cursor=False)
self.after_connection()
@@ -4726,14 +4765,13 @@ def cleanup(text):
"""
validates that the given text is clean: only contains [0-9a-zA-Z_]
"""
-
- if re.compile('[^0-9a-zA-Z_]').findall(text):
- raise SyntaxError, \
- 'only [0-9a-zA-Z_] allowed in table and field names, received %s' \
- % text
+ if not REGEX_ALPHANUMERIC.match(text):
+ raise SyntaxError, 'invalid table or field name: %s' % text
return text
class MongoDBAdapter(NoSQLAdapter):
+ drivers = ('pymongo',)
+
uploads_in_blob = True
types = {
@@ -4761,6 +4799,10 @@ class MongoDBAdapter(NoSQLAdapter):
pool_size=0,folder=None,db_codec ='UTF-8',
credential_decoder=IDENTITY, driver_args={},
adapter_args={}):
+ self.db = db
+ self.uri = uri
+ self.find_driver(adapter_args)
+
m=None
try:
#Since version 2
@@ -4775,8 +4817,6 @@ class MongoDBAdapter(NoSQLAdapter):
raise ImportError("Uriparser for mongodb is not available")
except:
raise SyntaxError("This type of uri is not supported by the mongodb uri parser")
- self.db = db
- self.uri = uri
self.dbengine = 'mongodb'
self.folder = folder
db['_lastsql'] = ''
@@ -4795,8 +4835,8 @@ class MongoDBAdapter(NoSQLAdapter):
raise SyntaxError("Database is required!")
def connect(uri=self.uri,m=m):
try:
- return pymongo.Connection(uri)[m.get('database')]
- except pymongo.errors.ConnectionFailure, inst:
+ return self.driver.Connection(uri)[m.get('database')]
+ except self.driver.errors.ConnectionFailure, inst:
raise SyntaxError, "The connection to " + uri + " could not be made"
except Exception, inst:
if inst == "cannot specify database without a username and password":
@@ -5333,6 +5373,8 @@ class MongoDBAdapter(NoSQLAdapter):
class IMAPAdapter(NoSQLAdapter):
+ drivers = ('imaplib',)
+
""" IMAP server adapter
This class is intended as an interface with
@@ -5459,7 +5501,8 @@ class IMAPAdapter(NoSQLAdapter):
}
dbengine = 'imap'
- driver = globals().get('imaplib',None)
+
+ REGEX_URI = re.compile('^(?P[^:]+)(\:(?P[^@]*))?@(?P[^\:@]+)(\:(?P[0-9]+))?$')
def __init__(self,
db,
@@ -5474,9 +5517,9 @@ class IMAPAdapter(NoSQLAdapter):
# db uri: user@example.com:password@imap.server.com:123
# TODO: max size adapter argument for preventing large mail transfers
- uri = uri.split("://")[1]
self.db = db
self.uri = uri
+ self.find_driver(adapter_args)
self.pool_size=pool_size
self.folder = folder
self.db_codec = db_codec
@@ -5487,6 +5530,7 @@ class IMAPAdapter(NoSQLAdapter):
self.charset = sys.getfilesystemencoding()
# imap class
self.imap4 = None
+ uri = uri.split("://")[1]
""" MESSAGE is an identifier for sequence number"""
@@ -5507,7 +5551,7 @@ class IMAPAdapter(NoSQLAdapter):
db['_lastsql'] = ''
- m = re.compile('^(?P[^:]+)(\:(?P[^@]*))?@(?P[^\:@]+)(\:(?P[0-9]+))?$').match(uri)
+ m = self.REGEX_URI.match(uri)
user = m.group('user')
password = m.group('password')
host = m.group('host')
@@ -6350,7 +6394,8 @@ def bar_decode_integer(value):
return [int(x) for x in value.split('|') if x.strip()]
def bar_decode_string(value):
- return [x.replace('||', '|') for x in string_unpack.split(value[1:-1]) if x.strip()]
+ return [x.replace('||', '|') for x in
+ REGEX_UNPACK.split(value[1:-1]) if x.strip()]
class Row(object):
@@ -6365,7 +6410,7 @@ class Row(object):
def __getitem__(self, key):
key=str(key)
- m = regex_table_field.match(key)
+ m = REGEX_TABLE_DOT_FIELD.match(key)
if key in self.get('_extra',{}):
return self._extra[key]
elif m:
@@ -6492,11 +6537,10 @@ def smart_query(fields,text):
n = str(field).lower()
if not n in field_map:
field_map[n] = field
- re_constants = re.compile('(\"[^\"]*?\")|(\'[^\']*?\')')
constants = {}
i = 0
while True:
- m = re_constants.search(text)
+ m = REGEX_CONST_STRING.search(text)
if not m: break
text = text[:m.start()]+('#%i' % i)+text[m.end():]
constants[str(i)] = m.group()[1:-1]
@@ -6706,7 +6750,7 @@ class DAL(object):
try:
if is_jdbc and not uri.startswith('jdbc:'):
uri = 'jdbc:'+uri
- self._dbname = regex_dbname.match(uri).group()
+ self._dbname = REGEX_DBNAME.match(uri).group()
if not self._dbname in ADAPTERS:
raise SyntaxError, "Error in URI '%s' or database not supported" % self._dbname
# notice that driver args or {} else driver_args
@@ -6819,8 +6863,8 @@ def index():
"""
db = self
- re1 = re.compile('^{[^\.]+\.[^\.]+(\.(lt|gt|le|ge|eq|ne|contains|startswith|year|month|day|hour|minute|second))?(\.not)?}$')
- re2 = re.compile('^.+\[.+\]$')
+ re1 = REGEX_SEARCH_PATTERN
+ re2 = REGEX_SQUARE_BRACKETS
def auto_table(table,base='',depth=0):
patterns = []
@@ -7023,7 +7067,7 @@ def index():
if not isinstance(tablename,str):
raise SyntaxError, "missing table name"
elif tablename.startswith('_') or hasattr(self,tablename) or \
- regex_python_keywords.match(tablename):
+ REGEX_PYTHON_KEYWORDS.match(tablename):
raise SyntaxError, 'invalid table name: %s' % tablename
elif tablename in self.tables:
raise SyntaxError, 'table already defined: %s' % tablename
@@ -7547,7 +7591,7 @@ class Table(object):
if rows:
return rows[0]
return None
- elif str(key).isdigit() or 'google' in drivers and isinstance(key, Key):
+ elif str(key).isdigit() or 'google' in DRIVERS and isinstance(key, Key):
return self._db(self._id == key).select(limitby=(0,1)).first()
elif key:
return ogetattr(self, str(key))
@@ -7673,21 +7717,25 @@ class Table(object):
to_compute.append((name,ofield))
# if field is required, check its default value
elif not update and not ofield.default is None:
- new_fields[name] = (ofield,ofield.default)
+ value = ofield.default
+ fields[name] = value
+ new_fields[name] = (ofield,value)
# if this is an update, user the update field instead
elif update and not ofield.update is None:
- new_fields[name] = (ofield,ofield.update)
+ value = ofield.update
+ fields[name] = value
+ new_fields[name] = (ofield,value)
# if the field is still not there but it should, error
elif not update and ofield.required:
raise RuntimeError, \
'Table: missing required field: %s' % name
# now deal with fields that are supposed to be computed
if to_compute:
- dummyrow = Row(new_fields)
+ row = Row(fields)
for name,ofield in to_compute:
# try compute it
try:
- new_fields[name] = (ofield,ofield.compute(dummyrow))
+ new_fields[name] = (ofield,ofield.compute(row))
except (KeyError, AttributeError):
# error sinlently unless field is required!
if ofield.required:
@@ -7931,6 +7979,7 @@ class Expression(object):
self.op = op
self.first = first
self.second = second
+ self._table = getattr(first,'_table',None)
### self._tablename = first._tablename ## CHECK
if not type and first and hasattr(first,'type'):
self.type = first.type
@@ -8329,7 +8378,7 @@ class Field(Expression):
self.second = None
self.name = fieldname = cleanup(fieldname)
if not isinstance(fieldname,str) or hasattr(Table,fieldname) or \
- fieldname[0] == '_' or regex_python_keywords.match(fieldname):
+ fieldname[0] == '_' or REGEX_PYTHON_KEYWORDS.match(fieldname):
raise SyntaxError, 'Field: invalid field name: %s' % fieldname
self.type = type if not isinstance(type, Table) else 'reference %s' % type
self.length = length if not length is None else DEFAULTLENGTH.get(self.type,512)
@@ -8383,8 +8432,8 @@ class Field(Expression):
elif not filename:
filename = file.name
filename = os.path.basename(filename.replace('/', os.sep)\
- .replace('\\', os.sep))
- m = re.compile('\.(?P\w{1,5})$').search(filename)
+ .replace('\\', os.sep))
+ m = REGEX_STORE_PATTERN.search(filename)
extension = m and m.group('e') or 'txt'
uuid_key = web2py_uuid().replace('-', '')[-16:]
encoded_filename = base64.b16encode(filename).lower()
@@ -8437,7 +8486,7 @@ class Field(Expression):
raise http.HTTP(404)
if self.authorize and not self.authorize(row):
raise http.HTTP(403)
- m = regex_content.match(name)
+ m = REGEX_UPLOAD_PATTERN.match(name)
if not m or not self.isattachment:
raise TypeError, 'Can\'t retrieve %s' % name
file_properties = self.retrieve_file_properties(name,path)
@@ -8462,11 +8511,11 @@ class Field(Expression):
if self.custom_retrieve_file_properties:
return self.custom_retrieve_file_properties(name, path)
try:
- m = regex_content.match(name)
+ m = REGEX_UPLOAD_PATTERN.match(name)
if not m or not self.isattachment:
raise TypeError, 'Can\'t retrieve %s file properties' % name
filename = base64.b16decode(m.group('name'), True)
- filename = regex_cleanup_fn.sub('_', filename)
+ filename = REGEX_CLEANUP_FN.sub('_', filename)
except (TypeError, AttributeError):
filename = name
if isinstance(self_uploadfield, str): # ## if file is in DB
@@ -8576,7 +8625,6 @@ class Query(object):
return Query(self.db,self.db._adapter.NOT,self)
-regex_quotes = re.compile("'[^']*'")
def xorify(orderby):
@@ -8932,18 +8980,24 @@ class Rows(object):
return None
return self[-1]
- def find(self,f):
+ def find(self,f,limitby=None):
"""
returns a new Rows object, a subset of the original object,
filtered by the function f
"""
- if not self.records:
+ if not self:
return Rows(self.db, [], self.colnames)
records = []
- for i in range(0,len(self)):
- row = self[i]
+ if limitby:
+ a,b = limitby
+ else:
+ a,b = 0,len(self)
+ k = 0
+ for row in self:
if f(row):
- records.append(self.records[i])
+ if a<=k: records.append(row)
+ k += 1
+ if k==b: break
return Rows(self.db, records, self.colnames)
def exclude(self, f):
@@ -9079,7 +9133,7 @@ class Rows(object):
for record in self:
row = []
for col in colnames:
- if not regex_table_field.match(col):
+ if not REGEX_TABLE_DOT_FIELD.match(col):
row.append(record._extra[col])
else:
(t, f) = col.split('.')
@@ -9098,8 +9152,7 @@ class Rows(object):
def xml(self,strict=False,row_name='row',rows_name='rows'):
"""
serializes the table using sqlhtml.SQLTABLE (if present)
- """
- alphanumeric = re.compile('[a-zA-Z]\w*')
+ """
if strict:
ncols = len(self.colnames)
def f(row,field,indent=' '):
@@ -9113,9 +9166,8 @@ class Rows(object):
indent,
field)
elif not callable(row):
- if alphanumeric.match(field):
- return '%s<%s>%s%s>' % \
- (indent,field,row,field)
+ if REGEX_ALPHANUMERIC.match(field):
+ return '%s<%s>%s%s>' % (indent,field,row,field)
else:
return '%s%s' % \
(indent,field,row)
@@ -9139,7 +9191,7 @@ class Rows(object):
def inner_loop(record, col):
(t, f) = col.split('.')
res = None
- if not regex_table_field.match(col):
+ if not REGEX_TABLE_DOT_FIELD.match(col):
key = col
res = record._extra[col]
else:
@@ -9403,8 +9455,3 @@ DAL.Table = Table # was necessary in gluon/globals.py session.connect
if __name__ == '__main__':
import doctest
doctest.testmod()
-
-
-
-
-
diff --git a/gluon/http.py b/gluon/http.py
index 548ac6d9..69356b85 100644
--- a/gluon/http.py
+++ b/gluon/http.py
@@ -76,7 +76,8 @@ class HTTP(BaseException):
self.headers['Set-Cookie'] = [
str(cookie)[11:] for cookie in cookies.values()]
- def to(self, responder):
+ def to(self, responder, env=None):
+ env = env or {}
status = self.status
headers = self.headers
if status in defined_status:
@@ -100,7 +101,9 @@ class HTTP(BaseException):
else:
rheaders.append((k, str(v)))
responder(status, rheaders)
- if isinstance(body,str):
+ if env.get('request_method','')=='HEAD':
+ return ['']
+ elif isinstance(body,str):
return [body]
elif hasattr(body, '__iter__'):
return body
diff --git a/gluon/languages.py b/gluon/languages.py
index f2d78c52..e7212136 100644
--- a/gluon/languages.py
+++ b/gluon/languages.py
@@ -12,13 +12,14 @@ Plural subsystem is created by Vladyslav Kozlovskyy (Ukraine)
import os
import re
+import pkgutil
from utf8 import Utf8
from cgi import escape
import portalocker
import logging
import marshal
import copy_reg
-from fileutils import abspath, listdir
+from fileutils import listdir
import settings
from cfs import getcfs
from thread import allocate_lock
@@ -31,6 +32,8 @@ __all__ = ['translator', 'findT', 'update_all_languages']
ospath = os.path
ostat = os.stat
osep = os.sep
+pjoin = os.path.join
+pdirname = os.path.dirname
isdir = os.path.isdir
is_gae = settings.global_settings.web2py_runtime_gae
@@ -80,7 +83,6 @@ regex_backslash = re.compile(r"\\([\\{}%])")
regex_plural = re.compile('%({.+?})')
regex_plural_dict = re.compile('^{(?P[^()[\]][^()[\]]*?)\((?P[^()\[\]]+)\)}$') # %%{word(varname or number)}
regex_plural_tuple = re.compile('^{(?P[^[\]()]+)(?:\[(?P\d+)\])?}$') # %%{word[index]} or %%{word}
-regex_plural_rules = re.compile('^plural_rules-[a-zA-Z]{2}(-[a-zA-Z]{2})?\.py$')
# UTF8 helper functions
def upper_fun(s):
@@ -213,47 +215,30 @@ def read_possible_languages(appdir):
langs['en'] = ('en', 'English', 0)
return langs
-def read_global_plural_rules(filename):
- """
- retrieve plural rules from rules/*plural_rules-lang*.py file.
-
- args:
- filename (str): plural_rules filename
-
- returns:
- (nplurals, get_plural_id, construct_plural_form, status)
- e.g.: (3, , , ok)
- """
- env = {}
- data = portalocker.read_locked(filename)
- try:
- exec(data) in env
- status='ok'
- except Exception, e:
- status='Syntax error in %s (%s)' % (filename, e)
- logging.error(status)
- nplurals = env.get('nplurals', DEFAULT_NPLURALS)
- get_plural_id = env.get('get_plural_id', DEFAULT_GET_PLURAL_ID)
- construct_plural_form = env.get('construct_plural_form',
- DEFAULT_CONSTRUCTOR_PLURAL_FORM)
- return (nplurals, get_plural_id, construct_plural_form, status)
-
-
def read_possible_plurals():
"""
create list of all possible plural rules files
result is cached to increase speed
"""
- pdir = abspath('gluon','contrib','rules')
- plurals = {}
- # scan rules directory for plural_rules-*.py files:
- for pname in os.listdir(pdir):
- if not isdir(pname) and regex_plural_rules.match(pname):
- lang = pname[13:-3]
- fname = ospath.join(pdir, pname)
- n, f1, f2, status = read_global_plural_rules(fname)
- if status == 'ok':
- plurals[lang] = (lang, n, f1, f2, pname)
+ try:
+ import gluon.contrib.plural_rules as package
+ plurals = {}
+ for importer, modname, ispkg in pkgutil.iter_modules(package.__path__):
+ if len(modname)==2:
+ module = __import__(package.__name__+'.'+modname)
+ lang = modname
+ pname = modname+'.py'
+ nplurals = getattr(module,'nplurals', DEFAULT_NPLURALS)
+ get_plural_id = getattr(
+ module,'get_plural_id',
+ DEFAULT_GET_PLURAL_ID)
+ construct_plural_form = getattr(
+ module,'construct_plural_form',
+ DEFAULT_CONSTRUCTOR_PLURAL_FORM)
+ plurals[lang] = (lang, nplurals, get_plural_id,
+ construct_plural_form, pname)
+ except ImportError:
+ logging.warn('Unable to import plural rules')
plurals['default'] = ('default',
DEFAULT_NPLURALS,
DEFAULT_GET_PLURAL_ID,
@@ -429,7 +414,7 @@ class translator(object):
self.request = request
self.folder = request.folder
self.langpath = ospath.join(self.folder,'languages')
- self.filenames = set(os.listdir(self.langpath))
+ self.filenames = set(os.listdir(self.langpath))
self.http_accept_language = request.env.http_accept_language
# self.cache # filled in self.force()
# self.accepted_language = None # filled in self.force()
@@ -442,7 +427,6 @@ class translator(object):
# self.plural_file = None # filled in self.force()
# self.plural_dict = None # filled in self.force()
# self.plural_status = None # filled in self.force()
-
self.requested_languages = \
self.force(self.http_accept_language)
self.lazy = True
@@ -506,7 +490,7 @@ class translator(object):
if int(n)==1:
return word
elif word:
- id = min(int(n)-1,1) # self.get_plural_id(abs(int(n)))
+ id = self.get_plural_id(abs(int(n)))
# id = 0 first plural form
# id = 1 second plural form
# etc.
@@ -816,8 +800,8 @@ def findT(path, language='en'):
"""
must be run by the admin app
"""
- filename = ospath.join(path, 'languages', language + '.py')
- sentences = read_dict(filename)
+ lang_file = ospath.join(path, 'languages', language + '.py')
+ sentences = read_dict(lang_file)
mp = ospath.join(path, 'models')
cp = ospath.join(path, 'controllers')
vp = ospath.join(path, 'views')
@@ -846,8 +830,9 @@ def findT(path, language='en'):
'en' if language in ('default', 'en') else language)
if not '!langname!' in sentences:
sentences['!langname!'] = (
- 'English' if language in ('default', 'en') else sentences['!langcode!'])
- write_dict(filename, sentences)
+ 'English' if language in ('default', 'en')
+ else sentences['!langcode!'])
+ write_dict(lang_file, sentences)
### important to allow safe session.flash=T(....)
def lazyT_unpickle(data):
diff --git a/gluon/main.py b/gluon/main.py
index efa30df7..94610f41 100644
--- a/gluon/main.py
+++ b/gluon/main.py
@@ -30,7 +30,7 @@ import string
import urllib2
from thread import allocate_lock
-from fileutils import abspath, write_file, parse_version
+from fileutils import abspath, write_file, parse_version, copystream
from settings import global_settings
from admin import add_path_first, create_missing_folders, create_missing_app_folders
from globals import current
@@ -84,7 +84,6 @@ from http import HTTP, redirect
from globals import Request, Response, Session
from compileapp import build_environment, run_models_in, \
run_controller_in, run_view_in
-from fileutils import copystream, parse_version
from contenttype import contenttype
from dal import BaseAdapter
from settings import global_settings
@@ -384,7 +383,7 @@ def wsgibase(environ, responder):
# ##################################################
eget = environ.get
- if not eget('PATH_INFO',None) and eget('REQUEST_URI',None):
+ if not eget('PATH_INFO') and eget('REQUEST_URI'):
# for fcgi, get path_info and
# query_string from request_uri
items = environ['REQUEST_URI'].split('?')
@@ -393,9 +392,14 @@ def wsgibase(environ, responder):
environ['QUERY_STRING'] = items[1]
else:
environ['QUERY_STRING'] = ''
- if not eget('HTTP_HOST',None):
+ elif not eget('REQUEST_URI'):
+ if eget('QUERY_STRING'):
+ environ['REQUEST_URI'] = eget('PATH_INFO') + '?' + eget('QUERY_STRING')
+ else:
+ environ['REQUEST_URI'] = eget('PATH_INFO')
+ if not eget('HTTP_HOST'):
environ['HTTP_HOST'] = \
- eget('SERVER_NAME')+':'+eget('SERVER_PORT')
+ eget('SERVER_NAME') + ':' + eget('SERVER_PORT')
(static_file, environ) = url_in(request, environ)
@@ -526,7 +530,8 @@ def wsgibase(environ, responder):
except HTTP, http_response:
if static_file:
- return http_response.to(responder)
+ return http_response.to(responder,env=env)
+
if request.body:
request.body.close()
@@ -636,7 +641,7 @@ def wsgibase(environ, responder):
return wsgibase(new_environ,responder)
if global_settings.web2py_crontype == 'soft':
newcron.softcron(global_settings.applications_parent).start()
- return http_response.to(responder)
+ return http_response.to(responder,env=env)
def save_password(password, port):
diff --git a/gluon/rewrite.py b/gluon/rewrite.py
index 731cee67..c7a1b211 100644
--- a/gluon/rewrite.py
+++ b/gluon/rewrite.py
@@ -38,6 +38,7 @@ thread = threading.local() # thread-local storage for routing params
regex_at = re.compile(r'(?(.*)')
+regex_full_url = re.compile(r'^(?Phttp|https|HTTP|HTTPS)\://(?P[^/]*)(?P.*)')
def _router_default():
"return new copy of default base router"
@@ -695,11 +696,14 @@ def regex_filter_out(url, e=None):
return url
-def filter_url(url, method='get', remote='0.0.0.0', out=False, app=False, lang=None,
- domain=(None,None), env=False, scheme=None, host=None, port=None):
- "doctest/unittest interface to regex_filter_in() and regex_filter_out()"
- regex_url = re.compile(r'^(?Phttp|https|HTTP|HTTPS)\://(?P[^/]*)(?P.*)')
- match = regex_url.match(url)
+def filter_url(url, method='get', remote='0.0.0.0',
+ out=False, app=False, lang=None,
+ domain=(None,None), env=False, scheme=None,
+ host=None, port=None):
+ """
+ doctest/unittest interface to regex_filter_in() and regex_filter_out()
+ """
+ match = regex_full_url.match(url)
urlscheme = match.group('scheme').lower()
urlhost = match.group('host').lower()
uri = match.group('uri')
@@ -994,6 +998,7 @@ class MapUrlIn(object):
static_file = pjoin(self.request.env.applications_parent,
'applications', self.application,
'static', file)
+ self.extension = None
log_rewrite("route: static=%s" % static_file)
return static_file
@@ -1053,7 +1058,7 @@ class MapUrlIn(object):
if self.map_hyphen:
uri = uri.replace('_', '-')
app = app.replace('_', '-')
- if self.extension != 'html':
+ if self.extension and self.extension != 'html':
uri += '.' + self.extension
if self.language:
uri = '/%s%s' % (self.language, uri)
@@ -1271,19 +1276,14 @@ def map_url_in(request, env, app=False):
# handle mapping of lang/static to static/lang in externally-rewritten URLs
# in case we have to handle them ourselves
if map.languages and map.map_static is False and map.arg0 == 'static' and map.args(1) in map.languages:
- if 'es' in map.languages:
- print 'handle static/lang %s' % map.args(1)
map.map_controller()
map.map_language()
else:
- if 'es' in map.languages:
- print 'NO handle static/lang %s' % map.args(1)
map.map_language()
map.map_controller()
static_file = map.map_static()
- if 'es' in map.languages:
- print 'static_file=%s' % static_file
if static_file:
+ map.update_request()
return (static_file, map.env)
map.map_function()
map.validate_args()
diff --git a/gluon/scheduler.py b/gluon/scheduler.py
index 9e585d39..b22019fd 100644
--- a/gluon/scheduler.py
+++ b/gluon/scheduler.py
@@ -88,7 +88,7 @@ except:
from simplejson import loads, dumps
-from gluon import DAL, Field, IS_NOT_EMPTY, IS_IN_SET, IS_NOT_IN_DB
+from gluon import DAL, Field, IS_NOT_EMPTY, IS_IN_SET, IS_NOT_IN_DB, IS_INT_IN_RANGE
from gluon.utils import web2py_uuid
@@ -262,7 +262,8 @@ class MetaScheduler(threading.Thread):
start = time.time()
- while p.is_alive() and (time.time()-start < task.timeout):
+ while p.is_alive() and (
+ not task.timeout or time.time()-start < task.timeout):
if tout:
try:
logging.debug(' partial output saved')
@@ -453,15 +454,20 @@ class Scheduler(MetaScheduler):
Field('args','text',default='[]',requires=TYPE(list)),
Field('vars','text',default='{}',requires=TYPE(dict)),
Field('enabled','boolean',default=True),
- Field('start_time','datetime',default=now),
+ Field('start_time','datetime',default=now, requires=IS_NOT_EMPTY()),
Field('next_run_time','datetime',default=now),
Field('stop_time','datetime'),
- Field('repeats','integer',default=1,comment="0=unlimited"),
- Field('retry_failed', 'integer', default=0, comment="-1=unlimited"),
- Field('period','integer',default=60,comment='seconds'),
- Field('timeout','integer',default=60,comment='seconds'),
+ Field('repeats','integer',default=1,comment="0=unlimited",
+ requires=IS_INT_IN_RANGE(0, None)),
+ Field('retry_failed', 'integer', default=0, comment="-1=unlimited",
+ requires=IS_INT_IN_RANGE(-1, None)),
+ Field('period','integer',default=60,comment='seconds',
+ requires=IS_INT_IN_RANGE(0, None)),
+ Field('timeout','integer',default=60,comment='seconds',
+ requires=IS_INT_IN_RANGE(0, None)),
Field('sync_output', 'integer', default=0,
- comment="update output every n sec: 0=never"),
+ comment="update output every n sec: 0=never",
+ requires=IS_INT_IN_RANGE(0, None)),
Field('times_run','integer',default=0,writable=False),
Field('times_failed','integer',default=0,writable=False),
Field('last_run_time','datetime',writable=False,readable=False),
@@ -869,4 +875,3 @@ def main():
if __name__=='__main__':
main()
-
diff --git a/gluon/sql.py b/gluon/sql.py
index 1a43f4cc..f49878de 100644
--- a/gluon/sql.py
+++ b/gluon/sql.py
@@ -1,8 +1,8 @@
# this file exists for backward compatibility
-__all__ = ['DAL','Field','drivers']
+__all__ = ['DAL','Field','DRIVERS']
-from dal import DAL, Field, Table, Query, Set, Expression, Row, Rows, drivers, BaseAdapter, SQLField, SQLTable, SQLXorable, SQLQuery, SQLSet, SQLRows, SQLStorage, SQLDB, GQLDB, SQLALL, SQLCustomType
+from dal import DAL, Field, Table, Query, Set, Expression, Row, Rows, DRIVERS, BaseAdapter, SQLField, SQLTable, SQLXorable, SQLQuery, SQLSet, SQLRows, SQLStorage, SQLDB, GQLDB, SQLALL, SQLCustomType
diff --git a/gluon/sqlhtml.py b/gluon/sqlhtml.py
index 20bfef25..959d40b3 100644
--- a/gluon/sqlhtml.py
+++ b/gluon/sqlhtml.py
@@ -24,7 +24,7 @@ from html import FORM, INPUT, LABEL, OPTION, SELECT, BUTTON
from html import TABLE, THEAD, TBODY, TR, TD, TH, STYLE
from html import URL, truncate_string, FIELDSET
from dal import DAL, Field, Table, Row, CALLABLETYPES, smart_query, \
- bar_encode, regex_table_field, Reference
+ bar_encode, Reference, REGEX_TABLE_DOT_FIELD
from storage import Storage
from utils import md5_hash
from validators import IS_EMPTY_OR, IS_NOT_EMPTY, IS_LIST_OF, IS_DATE, \
@@ -1042,20 +1042,19 @@ class SQLFORM(FORM):
# build a link
if record and linkto:
db = linkto.split('/')[-1]
- for (rtable, rfield) in table._referenced_by:
+ for rfld in table._referenced_by:
if keyed:
- rfld = table._db[rtable][rfield]
query = urllib.quote('%s.%s==%s' % (db,rfld,record[rfld.type[10:].split('.')[1]]))
else:
- query = urllib.quote('%s.%s==%s' % (db,table._db[rtable][rfield],record[self.id_field_name]))
- lname = olname = '%s.%s' % (rtable, rfield)
+ query = urllib.quote('%s.%s==%s' % (db,rfld,record[self.id_field_name]))
+ lname = olname = '%s.%s' % (rfld.tablename, rfld.name)
if ofields and not olname in ofields:
continue
if labels and lname in labels:
lname = labels[lname]
widget = A(lname,
_class='reference',
- _href='%s/%s?query=%s' % (linkto, rtable, query))
+ _href='%s/%s?query=%s' % (linkto, rfld.tablename, query))
xfields.append((olname.replace('.', '__')+SQLFORM.ID_ROW_SUFFIX,
'',widget,col3.get(olname,'')))
self.custom.linkto[olname.replace('.', '__')] = widget
@@ -1657,7 +1656,7 @@ class SQLFORM(FORM):
if user_signature:
if (args != request.args and user_signature and \
not URL.verify(request,user_signature=user_signature)) or \
- (not session.auth.user and \
+ (not (session.auth and session.auth.user) and \
('edit' in request.args or \
'create' in request.args or \
'delete' in request.args)):
@@ -1827,14 +1826,15 @@ class SQLFORM(FORM):
try:
dbset = dbset(SQLFORM.build_query(
fields,request.vars.get('keywords','')))
- rows = dbset.select()
+ rows = dbset.select(cacheable=True)
except Exception, e:
response.flash = T('Internal Error')
rows = []
else:
- rows = dbset.select()
+ rows = dbset.select(cacheable=True)
else:
- rows = dbset.select(left=left,orderby=orderby,*columns)
+ rows = dbset.select(left=left,orderby=orderby,
+ cacheable=True*columns)
if export_type in exportManager:
value = exportManager[export_type]
@@ -1893,7 +1893,8 @@ class SQLFORM(FORM):
try:
if left or groupby:
c = 'count(*)'
- nrows = dbset.select(c,left=left,groupby=groupby).first()[c]
+ nrows = dbset.select(c,left=left,cacheable=True,
+ groupby=groupby).first()[c]
else:
nrows = dbset.count()
except:
@@ -1977,7 +1978,9 @@ class SQLFORM(FORM):
try:
table_fields = [f for f in fields if f._tablename in tablenames]
- rows = dbset.select(left=left,orderby=orderby,groupby=groupby,limitby=limitby,*table_fields)
+ rows = dbset.select(left=left,orderby=orderby,
+ groupby=groupby,limitby=limitby,
+ cacheable=True,*table_fields)
except SyntaxError:
rows = None
error = T("Query Not Supported")
@@ -2187,12 +2190,14 @@ class SQLFORM(FORM):
LI(A(T(db[referee]._plural),
_class=trap_class(),
_href=url()),
- SPAN(divider,_class='divider'),_class='w2p_grid_breadcrumb_elem'))
+ SPAN(divider,_class='divider'),
+ _class='w2p_grid_breadcrumb_elem'))
if kwargs.get('details',True):
breadcrumbs.append(
LI(A(name,_class=trap_class(),
_href=url(args=['view',referee,id])),
- SPAN(divider,_class='divider'),_class='w2p_grid_breadcrumb_elem'))
+ SPAN(divider,_class='divider'),
+ _class='w2p_grid_breadcrumb_elem'))
nargs+=2
else:
break
@@ -2218,16 +2223,18 @@ class SQLFORM(FORM):
del kwargs[key]
check = {}
id_field_name = table._id.name
- for tablename,fieldname in table._referenced_by:
- if db[tablename][fieldname].readable:
- check[tablename] = check.get(tablename,[])+[fieldname]
+ for rfield in table._referenced_by:
+ if rfield.readable:
+ check[rfield.tablename] = \
+ check.get(rfield.tablename,[])+[rfield.name]
for tablename in sorted(check):
linked_fieldnames = check[tablename]
tb = db[tablename]
multiple_links = len(linked_fieldnames)>1
for fieldname in linked_fieldnames:
if linked_tables is None or tablename in linked_tables:
- t = T(tb._plural) if not multiple_links else T(tb._plural+'('+fieldname+')')
+ t = T(tb._plural) if not multiple_links else \
+ T(tb._plural+'('+fieldname+')')
args0 = tablename+'.'+fieldname
links.append(
lambda row,t=t,nargs=nargs,args0=args0:\
@@ -2561,7 +2568,7 @@ class ExportClass(object):
for record in self.rows:
row = []
for col in self.rows.colnames:
- if not regex_table_field.match(col):
+ if not REGEX_TABLE_DOT_FIELD.match(col):
row.append(record._extra[col])
else:
(t, f) = col.split('.')
diff --git a/gluon/storage.py b/gluon/storage.py
index 74611c6d..80e1c9af 100644
--- a/gluon/storage.py
+++ b/gluon/storage.py
@@ -136,7 +136,7 @@ class StorageList(Storage):
like Storage but missing elements default to [] instead of None
"""
def __getitem__(self,key):
- return self.__gteattr__(key)
+ return self.__getattr__(key)
def __getattr__(self, key):
if key in self:
return getattr(self,key)
diff --git a/gluon/template.py b/gluon/template.py
index a8481338..3d6442c0 100644
--- a/gluon/template.py
+++ b/gluon/template.py
@@ -177,7 +177,8 @@ class Content(BlockNode):
if isinstance(other, (list, tuple)):
# Must reverse so the order stays the same.
other.reverse()
- (self._insert(item, index) for item in other)
+ for item in other:
+ self._insert(item, index)
else:
self._insert(other, index)
diff --git a/gluon/tests/__init__.py b/gluon/tests/__init__.py
index 63f85d7c..a0fc28d4 100644
--- a/gluon/tests/__init__.py
+++ b/gluon/tests/__init__.py
@@ -8,3 +8,6 @@ from test_routes import *
from test_storage import *
from test_template import *
from test_utils import *
+from test_contribs import *
+from test_markmin import *
+# from test_web import *
diff --git a/gluon/tests/test.sh b/gluon/tests/test.sh
index a32b17c3..1e25f800 100755
--- a/gluon/tests/test.sh
+++ b/gluon/tests/test.sh
@@ -28,8 +28,10 @@ else
elif [ "$1" = "doctest" ]; then
# this has to run in gluon's parent; needs work
#
- # the problem is that doctests run this way have a very different environment,
- # apparently due to imports that don't happen in the normal course of running
+ # the problem is that doctests run this way
+ # have a very different environment,
+ # apparently due to imports that don't happen
+ # in the normal course of running
# doctest via __main__.
#
echo doctest not supported >&2
diff --git a/gluon/tests/test_cache.py b/gluon/tests/test_cache.py
index 15f29b6d..ec9b94d4 100644
--- a/gluon/tests/test_cache.py
+++ b/gluon/tests/test_cache.py
@@ -70,3 +70,5 @@ if __name__ == '__main__':
setUpModule() # pre-python-2.7
unittest.main()
tearDownModule()
+
+
diff --git a/gluon/tests/test_contribs.py b/gluon/tests/test_contribs.py
index 6af457ad..fbb4595f 100644
--- a/gluon/tests/test_contribs.py
+++ b/gluon/tests/test_contribs.py
@@ -14,7 +14,7 @@ else:
from utils import md5_hash
import contrib.fpdf as fpdf
import contrib.pyfpdf as pyfpdf
-
+
class TestContribs(unittest.TestCase):
""" Tests the contrib package """
@@ -28,14 +28,15 @@ class TestContribs(unittest.TestCase):
pdf = fpdf.FPDF()
pdf.add_page()
pdf.compress = False
- pdf.set_font('Arial', '',14)
+ pdf.set_font('Arial', '',14)
pdf.ln(10)
pdf.write(5, 'hello world')
pdf_out = pdf.output('', 'S')
-
+
self.assertTrue(fpdf.FPDF_VERSION in pdf_out, 'version string')
self.assertTrue('hello world' in pdf_out, 'sample message')
if __name__ == '__main__':
unittest.main()
+
diff --git a/gluon/tests/test_dal.py b/gluon/tests/test_dal.py
index 79e35c26..70cb09c1 100644
--- a/gluon/tests/test_dal.py
+++ b/gluon/tests/test_dal.py
@@ -176,9 +176,9 @@ class TestTable(unittest.TestCase):
self.assertRaises(SyntaxError, Table, None, 'test', None)
persons = Table(None, 'persons',
- Field('firstname','string'),
+ Field('firstname','string'),
Field('lastname', 'string'))
-
+
# Does it have the correct fields?
self.assert_(set(persons.fields).issuperset(set(['firstname',
@@ -411,12 +411,14 @@ class TestMinMaxSum(unittest.TestCase):
self.assertEqual(db.t.insert(a=3), 3)
s = db.t.a.min()
self.assertEqual(db(db.t.id > 0).select(s)[0]._extra[s], 1)
+ self.assertEqual(db(db.t.id > 0).select(s).first()[s], 1)
+ self.assertEqual(db().select(s).first()[s], 1)
s = db.t.a.max()
- self.assertEqual(db(db.t.id > 0).select(s)[0]._extra[s], 3)
+ self.assertEqual(db().select(s).first()[s], 3)
s = db.t.a.sum()
- self.assertEqual(db(db.t.id > 0).select(s)[0]._extra[s], 6)
+ self.assertEqual(db().select(s).first()[s], 6)
s = db.t.a.count()
- self.assertEqual(db(db.t.id > 0).select(s)[0]._extra[s], 3)
+ self.assertEqual(db().select(s).first()[s], 3)
db.t.drop()
@@ -515,6 +517,20 @@ class TestVirtualFields(unittest.TestCase):
db.t.drop()
db.commit()
+class TestComputedFields(unittest.TestCase):
+
+ def testRun(self):
+ db = DAL('sqlite:memory:')
+ db.define_table('t',
+ Field('a'),
+ Field('b',default='x'),
+ Field('c',compute=lambda r: r.a+r.b))
+ db.commit()
+ id = db.t.insert(a="z")
+ self.assertEqual(db.t[id].c,'zx')
+ db.t.drop()
+ db.commit()
+
class TestImportExportFields(unittest.TestCase):
def testRun(self):
@@ -533,7 +549,7 @@ class TestImportExportFields(unittest.TestCase):
db(db.pet).delete()
db(db.person).delete()
stream = cStringIO.StringIO(stream.getvalue())
- db.import_from_csv_file(stream)
+ db.import_from_csv_file(stream)
assert db(db.person.id==db.pet.friend)(db.person.name==db.pet.name).count()==10
db.pet.drop()
db.person.drop()
@@ -555,7 +571,7 @@ class TestImportExportUuidFields(unittest.TestCase):
stream = cStringIO.StringIO()
db.export_to_csv_file(stream)
stream = cStringIO.StringIO(stream.getvalue())
- db.import_from_csv_file(stream)
+ db.import_from_csv_file(stream)
assert db(db.person).count()==10
assert db(db.person.id==db.pet.friend)(db.person.name==db.pet.name).count()==20
db.pet.drop()
@@ -565,3 +581,4 @@ class TestImportExportUuidFields(unittest.TestCase):
if __name__ == '__main__':
unittest.main()
tearDownModule()
+
diff --git a/gluon/tests/test_html.py b/gluon/tests/test_html.py
index 752ed921..ff906b56 100644
--- a/gluon/tests/test_html.py
+++ b/gluon/tests/test_html.py
@@ -27,7 +27,7 @@ class TestBareHelpers(unittest.TestCase):
def testHR(self):
self.assertEqual(HR(_a='1', _b='2').xml(), '
')
-
+
def testIMG(self):
self.assertEqual(IMG(_a='1', _b='2').xml(),
'
')
@@ -209,3 +209,4 @@ class TestBareHelpers(unittest.TestCase):
if __name__ == '__main__':
unittest.main()
+
diff --git a/gluon/tests/test_is_url.py b/gluon/tests/test_is_url.py
index 60ffdd45..7a17496c 100644
--- a/gluon/tests/test_is_url.py
+++ b/gluon/tests/test_is_url.py
@@ -642,3 +642,4 @@ class TestUnicode(unittest.TestCase):
if __name__ == '__main__':
unittest.main()
+
diff --git a/gluon/tests/test_languages.py b/gluon/tests/test_languages.py
index bcff8de4..635eeea2 100644
--- a/gluon/tests/test_languages.py
+++ b/gluon/tests/test_languages.py
@@ -32,7 +32,7 @@ try:
return True
class TestLanguagesParallel(unittest.TestCase):
-
+
def setUp(self):
self.filename = tempfile.mktemp()
contents = dict()
@@ -46,7 +46,7 @@ try:
os.remove(self.filename)
except:
pass
-
+
def test_reads_and_writes(self):
readwriters = 10
pool = multiprocessing.Pool(processes = readwriters)
@@ -56,17 +56,17 @@ try:
class TestTranslations(unittest.TestCase):
-
- def setUp(self):
+
+ def setUp(self):
self.request = Storage()
self.request.folder = 'applications/welcome'
self.request.env = Storage()
self.request.env.http_accept_language = 'en'
-
-
+
+
def tearDown(self):
pass
-
+
def test_plain(self):
T = languages.translator(self.request)
self.assertEqual(str(T('Hello World')),
@@ -81,6 +81,10 @@ try:
'1 shop')
self.assertEqual(str(T('%s %%{shop[0]}', 2)),
'2 shops')
+ self.assertEqual(str(T('%s %%{quark[0]}', 1)),
+ '1 quark')
+ self.assertEqual(str(T('%s %%{quark[0]}', 2)),
+ '2 quarks')
self.assertEqual(str(T.M('**Hello World**')),
'Hello World')
T.force('it')
@@ -89,6 +93,7 @@ try:
except ImportError:
logging.warning("Skipped test case, no multiprocessing module.")
-
+
if __name__ == '__main__':
unittest.main()
+
diff --git a/gluon/tests/test_markmin.py b/gluon/tests/test_markmin.py
new file mode 100644
index 00000000..a300d3ed
--- /dev/null
+++ b/gluon/tests/test_markmin.py
@@ -0,0 +1,22 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+ Unit tests for running web2py
+"""
+import sys
+import os
+if os.path.isdir('gluon'):
+ sys.path.append(os.path.realpath('gluon'))
+else:
+ sys.path.append(os.path.realpath('../'))
+
+import unittest
+from gluon.contrib.markmin.markmin2html import run_doctests
+
+class TestMarkmin(unittest.TestCase):
+ def testMarkmin(self):
+ run_doctests()
+
+if __name__ == '__main__':
+ unittest.main()
+
diff --git a/gluon/tests/test_router.py b/gluon/tests/test_router.py
index 5cf7b1aa..f554f420 100644
--- a/gluon/tests/test_router.py
+++ b/gluon/tests/test_router.py
@@ -54,7 +54,7 @@ def setUpModule():
routes = open(abspath('applications', 'examples', 'routes.py'), 'w')
routes.write("routers=dict(examples=dict(default_function='exdef'))")
routes.close()
-
+
# create language files for examples app
for lang in ('en', 'it'):
os.mkdir(abspath('applications', 'examples', 'static', lang))
@@ -92,11 +92,11 @@ class TestRouter(unittest.TestCase):
self.assertRaises(SyntaxError, load, rdict=dict(BASE=dict(), app=dict(default_application="name")))
try:
# 2.7+ only
- self.assertRaisesRegexp(SyntaxError, "invalid syntax",
+ self.assertRaisesRegexp(SyntaxError, "invalid syntax",
load, data='x:y')
- self.assertRaisesRegexp(SyntaxError, "unknown key",
+ self.assertRaisesRegexp(SyntaxError, "unknown key",
load, rdict=dict(BASE=dict(badkey="value")))
- self.assertRaisesRegexp(SyntaxError, "BASE-only key",
+ self.assertRaisesRegexp(SyntaxError, "BASE-only key",
load, rdict=dict(BASE=dict(), app=dict(default_application="name")))
except AttributeError:
pass
@@ -133,9 +133,9 @@ class TestRouter(unittest.TestCase):
self.assertEqual(filter_url('http://domain.com/admin/default/abc', out=True), '/admin/abc')
def test_router_specific(self):
- """
- Test app-specific routes.py
-
+ """
+ Test app-specific routes.py
+
Note that make_apptree above created applications/examples/routes.py with a default_function.
"""
load(rdict=dict())
@@ -164,7 +164,7 @@ class TestRouter(unittest.TestCase):
self.assertEqual(filter_url('http://domain.com/welcome/default/index/arg1', out=True), '/index/arg1')
self.assertEqual(filter_url('http://domain.com/welcome/default/abc', out=True), '/abc')
self.assertEqual(filter_url('http://domain.com/welcome/default/admin', out=True), '/default/admin')
- self.assertEqual(filter_url('http://domain.com/welcome/static/abc', out=True),
+ self.assertEqual(filter_url('http://domain.com/welcome/static/abc', out=True),
'/welcome/static/abc')
self.assertEqual(filter_url('http://domain.com/welcome/appadmin/index', out=True), '/appadmin')
self.assertEqual(filter_url('http://domain.com/welcome/appadmin/abc', out=True), '/appadmin/abc')
@@ -183,7 +183,7 @@ class TestRouter(unittest.TestCase):
self.assertEqual(filter_url('http://domain.com/welcome/default/index', out=True), '/default')
self.assertEqual(filter_url('http://domain.com/welcome/default/index/arg1', out=True), '/default/index/arg1')
self.assertEqual(filter_url('http://domain.com/welcome/default/abc', out=True), '/default/abc')
- self.assertEqual(filter_url('http://domain.com/welcome/static/abc', out=True),
+ self.assertEqual(filter_url('http://domain.com/welcome/static/abc', out=True),
'/welcome/static/abc')
self.assertEqual(filter_url('http://domain.com/welcome/appadmin/index', out=True), '/appadmin')
self.assertEqual(filter_url('http://domain.com/welcome/appadmin/abc', out=True), '/appadmin/abc')
@@ -386,7 +386,7 @@ class TestRouter(unittest.TestCase):
self.assertEqual(filter_url('http://domain2.com/f2'), '/welcome/default/f2')
self.assertEqual(filter_url('http://domain2.com/other/f3'), '/welcome/other/f3')
-
+
def test_router_domains(self):
'''
Test URLs that map domains
@@ -429,7 +429,7 @@ class TestRouter(unittest.TestCase):
self.assertEqual(filter_url('http://domain1.com/abc.css'), '/app1/c1/abc.css')
self.assertEqual(filter_url('http://domain1.com/index/abc'), "/app1/c1/index ['abc']")
self.assertEqual(filter_url('http://domain2.com/app1'), "/app2a/c2a/app1")
-
+
self.assertEqual(filter_url('https://domain1.com/app1/ctr/fcn', domain=('app1',None), out=True), "/ctr/fcn")
self.assertEqual(filter_url('https://www.domain1.com/app1/ctr/fcn', domain=('app1',None), out=True), "/ctr/fcn")
@@ -534,7 +534,7 @@ class TestRouter(unittest.TestCase):
self.assertEqual(filter_url('https://domain.com/init/ctr/index', out=True), "/ctr")
self.assertEqual(filter_url('http://domain.com/init/default/fcn?query', out=True), "/fcn?query")
self.assertEqual(filter_url('http://domain.com/init/default/fcn#anchor', out=True), "/fcn#anchor")
- self.assertEqual(filter_url('http://domain.com/init/default/fcn?query#anchor', out=True),
+ self.assertEqual(filter_url('http://domain.com/init/default/fcn?query#anchor', out=True),
"/fcn?query#anchor")
router_out['BASE']['map_static'] = True
@@ -576,11 +576,11 @@ class TestRouter(unittest.TestCase):
),
)
load(rdict=router_functions)
-
+
# outbound
self.assertEqual(str(URL(a='init', c='default', f='f', args=['arg1'])), "/init/f/arg1")
self.assertEqual(str(URL(a='init', c='default', f='index', args=['arg1'])), "/init/index/arg1")
-
+
self.assertEqual(str(URL(a='app', c='default', f='index', args=['arg1'])), "/arg1")
self.assertEqual(str(URL(a='app', c='default', f='user', args=['arg1'])), "/user/arg1")
self.assertEqual(str(URL(a='app', c='default', f='user', args=['index'])), "/user/index")
@@ -631,7 +631,7 @@ class TestRouter(unittest.TestCase):
)
load(rdict=router_functions)
-
+
# outbound
self.assertEqual(str(URL(a='init', c='default', f='index', args=['arg1'])), "/arg1")
self.assertEqual(str(URL(a='init', c='default', f='user', args=['arg1'])), "/user/arg1")
@@ -673,18 +673,18 @@ class TestRouter(unittest.TestCase):
)
load(rdict=router_hyphen)
self.assertEqual(filter_url('http://domain.com/init/default/fcn_1', out=True), "/fcn_1")
- self.assertEqual(filter_url('http://domain.com/static/filename-with_underscore'),
+ self.assertEqual(filter_url('http://domain.com/static/filename-with_underscore'),
"%s/applications/init/static/filename-with_underscore" % root)
- self.assertEqual(filter_url('http://domain.com/init/static/filename-with_underscore', out=True),
+ self.assertEqual(filter_url('http://domain.com/init/static/filename-with_underscore', out=True),
"/init/static/filename-with_underscore")
- self.assertEqual(filter_url('http://domain.com/app2/fcn_1'),
+ self.assertEqual(filter_url('http://domain.com/app2/fcn_1'),
"/app2/default/fcn_1")
- self.assertEqual(filter_url('http://domain.com/app2/ctr/fcn_1', domain=('app2',None), out=True),
+ self.assertEqual(filter_url('http://domain.com/app2/ctr/fcn_1', domain=('app2',None), out=True),
"/ctr/fcn_1")
- self.assertEqual(filter_url('http://domain.com/app2/static/filename-with_underscore', domain=('app2',None), out=True),
+ self.assertEqual(filter_url('http://domain.com/app2/static/filename-with_underscore', domain=('app2',None), out=True),
"/app2/static/filename-with_underscore")
- self.assertEqual(filter_url('http://domain.com/app2/static/filename-with_underscore'),
+ self.assertEqual(filter_url('http://domain.com/app2/static/filename-with_underscore'),
"%s/applications/app2/static/filename-with_underscore" % root)
self.assertEqual(str(URL(a='init', c='default', f='a_b')), "/a_b")
@@ -731,7 +731,7 @@ class TestRouter(unittest.TestCase):
self.assertEqual(filter_url('https://domain.com/admin/static/file', lang='it', out=True), "/admin/it/static/file")
self.assertEqual(filter_url('https://domain.com/admin/static/file', lang='it-it', out=True), "/admin/it-it/static/file")
self.assertEqual(filter_url('https://domain.com/welcome/ctr/fcn', lang='it', out=True), "/welcome/ctr/fcn")
- self.assertEqual(filter_url('https://domain.com/welcome/ctr/fcn', lang='es', out=True), "/welcome/ctr/fcn")
+ self.assertEqual(filter_url('https://domain.com/welcome/ctr/fcn', lang='es', out=True), "/welcome/ctr/fcn")
router_lang['admin']['map_static'] = True
load(rdict=router_lang)
@@ -742,7 +742,7 @@ class TestRouter(unittest.TestCase):
self.assertEqual(filter_url('https://domain.com/admin/static/file', lang='it', out=True), "/it/static/file")
self.assertEqual(filter_url('https://domain.com/admin/static/file', lang='it-it', out=True), "/it-it/static/file")
self.assertEqual(filter_url('https://domain.com/welcome/ctr/fcn', lang='it', out=True), "/welcome/ctr/fcn")
- self.assertEqual(filter_url('https://domain.com/welcome/ctr/fcn', lang='es', out=True), "/welcome/ctr/fcn")
+ self.assertEqual(filter_url('https://domain.com/welcome/ctr/fcn', lang='es', out=True), "/welcome/ctr/fcn")
router_lang['admin']['map_static'] = False
router_lang['examples']['map_static'] = False
@@ -754,7 +754,7 @@ class TestRouter(unittest.TestCase):
self.assertEqual(filter_url('https://domain.com/admin/static/file', lang='it', out=True), "/admin/static/it/file")
self.assertEqual(filter_url('https://domain.com/admin/static/file', lang='it-it', out=True), "/admin/static/it-it/file")
self.assertEqual(filter_url('https://domain.com/welcome/ctr/fcn', lang='it', out=True), "/welcome/ctr/fcn")
- self.assertEqual(filter_url('https://domain.com/welcome/ctr/fcn', lang='es', out=True), "/welcome/ctr/fcn")
+ self.assertEqual(filter_url('https://domain.com/welcome/ctr/fcn', lang='es', out=True), "/welcome/ctr/fcn")
self.assertEqual(filter_url('http://domain.com/static/file'), "%s/applications/admin/static/file" % root)
self.assertEqual(filter_url('http://domain.com/en/static/file'), "%s/applications/admin/static/file" % root)
self.assertEqual(filter_url('http://domain.com/examples/en/static/file'), "%s/applications/examples/static/en/file" % root)
@@ -865,21 +865,21 @@ class TestRouter(unittest.TestCase):
Test URL args parsing/generation
'''
load(rdict=dict())
- self.assertEqual(filter_url('http://domain.com/init/default/f/arg1'),
+ self.assertEqual(filter_url('http://domain.com/init/default/f/arg1'),
"/init/default/f ['arg1']")
- self.assertEqual(filter_url('http://domain.com/init/default/f/arg1/'),
+ self.assertEqual(filter_url('http://domain.com/init/default/f/arg1/'),
"/init/default/f ['arg1']")
- self.assertEqual(filter_url('http://domain.com/init/default/f/arg1//'),
+ self.assertEqual(filter_url('http://domain.com/init/default/f/arg1//'),
"/init/default/f ['arg1', '']")
- self.assertEqual(filter_url('http://domain.com/init/default/f//arg1'),
+ self.assertEqual(filter_url('http://domain.com/init/default/f//arg1'),
"/init/default/f ['', 'arg1']")
- self.assertEqual(filter_url('http://domain.com/init/default/f/arg1/arg2'),
+ self.assertEqual(filter_url('http://domain.com/init/default/f/arg1/arg2'),
"/init/default/f ['arg1', 'arg2']")
- self.assertEqual(filter_url('http://domain.com/init/default/f/arg1//arg2'),
+ self.assertEqual(filter_url('http://domain.com/init/default/f/arg1//arg2'),
"/init/default/f ['arg1', '', 'arg2']")
- self.assertEqual(filter_url('http://domain.com/init/default/f/arg1//arg3/'),
+ self.assertEqual(filter_url('http://domain.com/init/default/f/arg1//arg3/'),
"/init/default/f ['arg1', '', 'arg3']")
- self.assertEqual(filter_url('http://domain.com/init/default/f/arg1//arg3//'),
+ self.assertEqual(filter_url('http://domain.com/init/default/f/arg1//arg3//'),
"/init/default/f ['arg1', '', 'arg3', '']")
self.assertEqual(filter_url('http://domain.com/init/default/f', out=True), "/f")
@@ -903,12 +903,12 @@ class TestRouter(unittest.TestCase):
load(rdict=dict())
self.assertEqual(str(URL(a='a', c='c', f='f', anchor='anchor')), "/a/c/f#anchor")
args = ['a1', 'a2']
- self.assertEqual(str(URL(a='a', c='c', f='f', args=args, anchor='anchor')),
+ self.assertEqual(str(URL(a='a', c='c', f='f', args=args, anchor='anchor')),
"/a/c/f/a1/a2#anchor")
vars = dict(v1=1, v2=2)
- self.assertEqual(str(URL(a='a', c='c', f='f', vars=vars, anchor='anchor')),
+ self.assertEqual(str(URL(a='a', c='c', f='f', vars=vars, anchor='anchor')),
"/a/c/f?v1=1&v2=2#anchor")
- self.assertEqual(str(URL(a='a', c='c', f='f', args=args, vars=vars, anchor='anchor')),
+ self.assertEqual(str(URL(a='a', c='c', f='f', args=args, vars=vars, anchor='anchor')),
"/a/c/f/a1/a2?v1=1&v2=2#anchor")
self.assertEqual(str(URL(a='init', c='default', f='index')),
"/")
@@ -938,11 +938,11 @@ class TestRouter(unittest.TestCase):
),
)
load(rdict=router_path_prefix)
- self.assertEqual(str(URL(a='a1', c='c1a', f='f')),
+ self.assertEqual(str(URL(a='a1', c='c1a', f='f')),
"/path/to/apps/c1a/f")
- self.assertEqual(str(URL(a='a2', c='c', f='f')),
+ self.assertEqual(str(URL(a='a2', c='c', f='f')),
"/path/to/apps/a2/c/f")
- self.assertEqual(str(URL(a='a2', c='c2', f='f')),
+ self.assertEqual(str(URL(a='a2', c='c2', f='f')),
"/path/to/apps/a2/c2/f")
self.assertEqual(filter_url('http://domain.com/a1/'), "/a1/default/index")
self.assertEqual(filter_url('http://domain.com/path/to/apps/a1/'), "/a1/default/index")
@@ -958,35 +958,35 @@ class TestRouter(unittest.TestCase):
r.env.http_host = 'domain.com'
r.env.wsgi_url_scheme = 'httpx' # distinguish incoming scheme
self.assertEqual(str(URL(r=r, a='a', c='c', f='f')), "/a/c/f")
- self.assertEqual(str(URL(r=r, a='a', c='c', f='f', host=True)),
+ self.assertEqual(str(URL(r=r, a='a', c='c', f='f', host=True)),
"httpx://domain.com/a/c/f")
- self.assertEqual(str(URL(r=r, a='a', c='c', f='f', host='host.com')),
+ self.assertEqual(str(URL(r=r, a='a', c='c', f='f', host='host.com')),
"httpx://host.com/a/c/f")
- self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme=True)),
+ self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme=True)),
"httpx://domain.com/a/c/f")
- self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme=False)),
+ self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme=False)),
"/a/c/f")
- self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme='https')),
+ self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme='https')),
"https://domain.com/a/c/f")
- self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme='wss')),
+ self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme='wss')),
"wss://domain.com/a/c/f")
- self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme=True, host=True)),
+ self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme=True, host=True)),
"httpx://domain.com/a/c/f")
- self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme='https', host=True)),
+ self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme='https', host=True)),
"https://domain.com/a/c/f")
- self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme=False, host=True)),
+ self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme=False, host=True)),
"httpx://domain.com/a/c/f")
- self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme=True, host='host.com')),
+ self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme=True, host='host.com')),
"httpx://host.com/a/c/f")
- self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme=False, host='host.com')),
+ self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme=False, host='host.com')),
"httpx://host.com/a/c/f")
- self.assertEqual(str(URL(r=r, a='a', c='c', f='f', port=1234)),
+ self.assertEqual(str(URL(r=r, a='a', c='c', f='f', port=1234)),
"httpx://domain.com:1234/a/c/f")
- self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme=True, port=1234)),
+ self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme=True, port=1234)),
"httpx://domain.com:1234/a/c/f")
- self.assertEqual(str(URL(r=r, a='a', c='c', f='f', host='host.com', port=1234)),
+ self.assertEqual(str(URL(r=r, a='a', c='c', f='f', host='host.com', port=1234)),
"httpx://host.com:1234/a/c/f")
- self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme='wss', host='host.com', port=1234)),
+ self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme='wss', host='host.com', port=1234)),
"wss://host.com:1234/a/c/f")
def test_request_uri(self):
@@ -995,15 +995,15 @@ class TestRouter(unittest.TestCase):
'''
load(rdict=dict())
- self.assertEqual(filter_url('http://domain.com/abc', env=True).request_uri,
+ self.assertEqual(filter_url('http://domain.com/abc', env=True).request_uri,
'/init/default/abc')
- self.assertEqual(filter_url('http://domain.com/abc?def', env=True).request_uri,
+ self.assertEqual(filter_url('http://domain.com/abc?def', env=True).request_uri,
'/init/default/abc?def')
- self.assertEqual(filter_url('http://domain.com/index/abc', env=True).request_uri,
+ self.assertEqual(filter_url('http://domain.com/index/abc', env=True).request_uri,
"/init/default/index/abc")
- self.assertEqual(filter_url('http://domain.com/abc/def', env=True).request_uri,
+ self.assertEqual(filter_url('http://domain.com/abc/def', env=True).request_uri,
"/init/default/abc/def")
- self.assertEqual(filter_url('http://domain.com/index/a%20bc', env=True).request_uri,
+ self.assertEqual(filter_url('http://domain.com/index/a%20bc', env=True).request_uri,
"/init/default/index/a%20bc")
def test_request_collide(self):
@@ -1022,7 +1022,7 @@ class TestRouter(unittest.TestCase):
),
)
load(rdict=router_collide)
-
+
# basic inbound
self.assertEqual(filter_url('http://ex.domain.com'), '/examples/default/exdef')
self.assertEqual(filter_url('http://ad.domain.com'), '/admin/default/index')
@@ -1053,3 +1053,4 @@ if __name__ == '__main__':
setUpModule() # pre-2.7
unittest.main()
tearDownModule()
+
diff --git a/gluon/tests/test_routes.py b/gluon/tests/test_routes.py
index c3b171d6..abda14dc 100644
--- a/gluon/tests/test_routes.py
+++ b/gluon/tests/test_routes.py
@@ -112,9 +112,9 @@ routes_in = (
self.assertEqual(filter_url('http://localhost:8000/service/person/create?var1=val1'), "/app/default/call ['json', 'create'] ?model=person&var1=val1")
def test_routes_specific(self):
- """
- Test app-specific routes.py
-
+ """
+ Test app-specific routes.py
+
Note that make_apptree above created applications/examples/routes.py with a default_function.
"""
data = r'''
@@ -190,59 +190,59 @@ default_application = 'defapp'
Test URL args parsing/generation
'''
data = r'''routes_in = [
- ('/robots.txt', '/welcome/static/robots.txt'),
- ('/favicon.ico', '/welcome/static/favicon.ico'),
- ('/admin$anything', '/admin$anything'),
- ('.*:https?://(.*\\.)?domain1.com:$method /', '/app1/default'),
- ('.*:https?://(.*\\.)?domain1.com:$method /static/$anything', '/app1/static/$anything'),
- ('.*:https?://(.*\\.)?domain1.com:$method /appadmin/$anything', '/app1/appadmin/$anything'),
- ('.*:https?://(.*\\.)?domain1.com:$method /$anything', '/app1/default/$anything'),
- ('.*:https?://(.*\\.)?domain2.com:$method /', '/app2/default'),
- ('.*:https?://(.*\\.)?domain2.com:$method /static/$anything', '/app2/static/$anything'),
- ('.*:https?://(.*\\.)?domain2.com:$method /appadmin/$anything', '/app2/appadmin/$anything'),
- ('.*:https?://(.*\\.)?domain2.com:$method /$anything', '/app2/default/$anything'),
- ('.*:https?://(.*\\.)?domain3.com:$method /', '/app3/defcon3'),
- ('.*:https?://(.*\\.)?domain3.com:$method /static/$anything', '/app3/static/$anything'),
- ('.*:https?://(.*\\.)?domain3.com:$method /appadmin/$anything', '/app3/appadmin/$anything'),
+ ('/robots.txt', '/welcome/static/robots.txt'),
+ ('/favicon.ico', '/welcome/static/favicon.ico'),
+ ('/admin$anything', '/admin$anything'),
+ ('.*:https?://(.*\\.)?domain1.com:$method /', '/app1/default'),
+ ('.*:https?://(.*\\.)?domain1.com:$method /static/$anything', '/app1/static/$anything'),
+ ('.*:https?://(.*\\.)?domain1.com:$method /appadmin/$anything', '/app1/appadmin/$anything'),
+ ('.*:https?://(.*\\.)?domain1.com:$method /$anything', '/app1/default/$anything'),
+ ('.*:https?://(.*\\.)?domain2.com:$method /', '/app2/default'),
+ ('.*:https?://(.*\\.)?domain2.com:$method /static/$anything', '/app2/static/$anything'),
+ ('.*:https?://(.*\\.)?domain2.com:$method /appadmin/$anything', '/app2/appadmin/$anything'),
+ ('.*:https?://(.*\\.)?domain2.com:$method /$anything', '/app2/default/$anything'),
+ ('.*:https?://(.*\\.)?domain3.com:$method /', '/app3/defcon3'),
+ ('.*:https?://(.*\\.)?domain3.com:$method /static/$anything', '/app3/static/$anything'),
+ ('.*:https?://(.*\\.)?domain3.com:$method /appadmin/$anything', '/app3/appadmin/$anything'),
('.*:https?://(.*\\.)?domain3.com:$method /$anything', '/app3/defcon3/$anything'),
- ('/', '/welcome/default'),
- ('/welcome/default/$anything', '/welcome/default/$anything'),
- ('/welcome/$anything', '/welcome/default/$anything'),
- ('/static/$anything', '/welcome/static/$anything'),
- ('/appadmin/$anything', '/welcome/appadmin/$anything'),
- ('/$anything', '/welcome/default/$anything'),
+ ('/', '/welcome/default'),
+ ('/welcome/default/$anything', '/welcome/default/$anything'),
+ ('/welcome/$anything', '/welcome/default/$anything'),
+ ('/static/$anything', '/welcome/static/$anything'),
+ ('/appadmin/$anything', '/welcome/appadmin/$anything'),
+ ('/$anything', '/welcome/default/$anything'),
]
routes_out = [
- ('/welcome/static/$anything', '/static/$anything'),
- ('/welcome/appadmin/$anything', '/appadmin/$anything'),
- ('/welcome/default/$anything', '/$anything'),
- ('/app1/static/$anything', '/static/$anything'),
- ('/app1/appadmin/$anything', '/appadmin/$anything'),
- ('/app1/default/$anything', '/$anything'),
- ('/app2/static/$anything', '/static/$anything'),
- ('/app2/appadmin/$anything', '/appadmin/$anything'),
- ('/app2/default/$anything', '/$anything'),
- ('/app3/static/$anything', '/static/$anything'),
- ('/app3/appadmin/$anything', '/appadmin/$anything'),
+ ('/welcome/static/$anything', '/static/$anything'),
+ ('/welcome/appadmin/$anything', '/appadmin/$anything'),
+ ('/welcome/default/$anything', '/$anything'),
+ ('/app1/static/$anything', '/static/$anything'),
+ ('/app1/appadmin/$anything', '/appadmin/$anything'),
+ ('/app1/default/$anything', '/$anything'),
+ ('/app2/static/$anything', '/static/$anything'),
+ ('/app2/appadmin/$anything', '/appadmin/$anything'),
+ ('/app2/default/$anything', '/$anything'),
+ ('/app3/static/$anything', '/static/$anything'),
+ ('/app3/appadmin/$anything', '/appadmin/$anything'),
('/app3/defcon3/$anything', '/$anything')
]
'''
load(data=data)
- self.assertEqual(filter_url('http://domain.com/welcome/default/f/arg1'),
+ self.assertEqual(filter_url('http://domain.com/welcome/default/f/arg1'),
"/welcome/default/f ['arg1']")
- self.assertEqual(filter_url('http://domain.com/welcome/default/f/arg1/'),
+ self.assertEqual(filter_url('http://domain.com/welcome/default/f/arg1/'),
"/welcome/default/f ['arg1']")
- self.assertEqual(filter_url('http://domain.com/welcome/default/f/arg1//'),
+ self.assertEqual(filter_url('http://domain.com/welcome/default/f/arg1//'),
"/welcome/default/f ['arg1', '']")
- self.assertEqual(filter_url('http://domain.com/welcome/default/f//arg1'),
+ self.assertEqual(filter_url('http://domain.com/welcome/default/f//arg1'),
"/welcome/default/f ['', 'arg1']")
- self.assertEqual(filter_url('http://domain.com/welcome/default/f/arg1/arg2'),
+ self.assertEqual(filter_url('http://domain.com/welcome/default/f/arg1/arg2'),
"/welcome/default/f ['arg1', 'arg2']")
- self.assertEqual(filter_url('http://domain.com/welcome/default/f/arg1//arg2'),
+ self.assertEqual(filter_url('http://domain.com/welcome/default/f/arg1//arg2'),
"/welcome/default/f ['arg1', '', 'arg2']")
- self.assertEqual(filter_url('http://domain.com/welcome/default/f/arg1//arg3/'),
+ self.assertEqual(filter_url('http://domain.com/welcome/default/f/arg1//arg3/'),
"/welcome/default/f ['arg1', '', 'arg3']")
- self.assertEqual(filter_url('http://domain.com/welcome/default/f/arg1//arg3//'),
+ self.assertEqual(filter_url('http://domain.com/welcome/default/f/arg1//arg3//'),
"/welcome/default/f ['arg1', '', 'arg3', '']")
self.assertEqual(filter_url('http://domain.com/welcome/default/f', out=True), "/f")
@@ -263,16 +263,16 @@ routes_out = [
load(data='')
self.assertEqual(str(URL(a='a', c='c', f='f', anchor='anchor')), "/a/c/f#anchor")
args = ['a1', 'a2']
- self.assertEqual(str(URL(a='a', c='c', f='f', args=args, anchor='anchor')),
+ self.assertEqual(str(URL(a='a', c='c', f='f', args=args, anchor='anchor')),
"/a/c/f/a1/a2#anchor")
vars = dict(v1=1, v2=2)
- self.assertEqual(str(URL(a='a', c='c', f='f', vars=vars, anchor='anchor')),
+ self.assertEqual(str(URL(a='a', c='c', f='f', vars=vars, anchor='anchor')),
"/a/c/f?v1=1&v2=2#anchor")
- self.assertEqual(str(URL(a='a', c='c', f='f', args=args, vars=vars, anchor='anchor')),
+ self.assertEqual(str(URL(a='a', c='c', f='f', args=args, vars=vars, anchor='anchor')),
"/a/c/f/a1/a2?v1=1&v2=2#anchor")
data = r'''routes_out = [
- ('/init/default/index', '/'),
+ ('/init/default/index', '/'),
]'''
load(data=data)
self.assertEqual(str(URL(a='init', c='default', f='index')),
@@ -281,7 +281,7 @@ routes_out = [
"/init/default/index#anchor")
data = r'''routes_out = [
- (r'/init/default/index(?P(#.*)?)', r'/\g'),
+ (r'/init/default/index(?P(#.*)?)', r'/\g'),
]'''
load(data=data)
self.assertEqual(str(URL(a='init', c='default', f='index')),
@@ -290,7 +290,7 @@ routes_out = [
"/#anchor")
data = r'''routes_out = [
- (r'/init/default/index(?P([?#].*)?)', r'/\g'),
+ (r'/init/default/index(?P([?#].*)?)', r'/\g'),
]'''
load(data=data)
self.assertEqual(str(URL(a='init', c='default', f='index')),
@@ -313,35 +313,35 @@ routes_out = [
r.env.http_host = 'domain.com'
r.env.wsgi_url_scheme = 'httpx' # distinguish incoming scheme
self.assertEqual(str(URL(r=r, a='a', c='c', f='f')), "/a/c/f")
- self.assertEqual(str(URL(r=r, a='a', c='c', f='f', host=True)),
+ self.assertEqual(str(URL(r=r, a='a', c='c', f='f', host=True)),
"httpx://domain.com/a/c/f")
- self.assertEqual(str(URL(r=r, a='a', c='c', f='f', host='host.com')),
+ self.assertEqual(str(URL(r=r, a='a', c='c', f='f', host='host.com')),
"httpx://host.com/a/c/f")
- self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme=True)),
+ self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme=True)),
"httpx://domain.com/a/c/f")
- self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme=False)),
+ self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme=False)),
"/a/c/f")
- self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme='https')),
+ self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme='https')),
"https://domain.com/a/c/f")
- self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme='wss')),
+ self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme='wss')),
"wss://domain.com/a/c/f")
- self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme=True, host=True)),
+ self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme=True, host=True)),
"httpx://domain.com/a/c/f")
- self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme='https', host=True)),
+ self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme='https', host=True)),
"https://domain.com/a/c/f")
- self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme=False, host=True)),
+ self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme=False, host=True)),
"httpx://domain.com/a/c/f")
- self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme=True, host='host.com')),
+ self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme=True, host='host.com')),
"httpx://host.com/a/c/f")
- self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme=False, host='host.com')),
+ self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme=False, host='host.com')),
"httpx://host.com/a/c/f")
- self.assertEqual(str(URL(r=r, a='a', c='c', f='f', port=1234)),
+ self.assertEqual(str(URL(r=r, a='a', c='c', f='f', port=1234)),
"httpx://domain.com:1234/a/c/f")
- self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme=True, port=1234)),
+ self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme=True, port=1234)),
"httpx://domain.com:1234/a/c/f")
- self.assertEqual(str(URL(r=r, a='a', c='c', f='f', host='host.com', port=1234)),
+ self.assertEqual(str(URL(r=r, a='a', c='c', f='f', host='host.com', port=1234)),
"httpx://host.com:1234/a/c/f")
- self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme='wss', host='host.com', port=1234)),
+ self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme='wss', host='host.com', port=1234)),
"wss://host.com:1234/a/c/f")
def test_request_uri(self):
@@ -349,18 +349,18 @@ routes_out = [
Test REQUEST_URI in env
'''
data = r'''routes_in = [
- ('/abc', '/init/default/abc'),
- ('/index/$anything', '/init/default/index/$anything'),
+ ('/abc', '/init/default/abc'),
+ ('/index/$anything', '/init/default/index/$anything'),
]
'''
load(data=data)
- self.assertEqual(filter_url('http://domain.com/abc', env=True).request_uri,
+ self.assertEqual(filter_url('http://domain.com/abc', env=True).request_uri,
'/init/default/abc')
- self.assertEqual(filter_url('http://domain.com/abc?def', env=True).request_uri,
+ self.assertEqual(filter_url('http://domain.com/abc?def', env=True).request_uri,
'/init/default/abc?def')
- self.assertEqual(filter_url('http://domain.com/index/abc', env=True).request_uri,
+ self.assertEqual(filter_url('http://domain.com/index/abc', env=True).request_uri,
"/init/default/index/abc")
- self.assertEqual(filter_url('http://domain.com/index/a%20bc', env=True).request_uri,
+ self.assertEqual(filter_url('http://domain.com/index/a%20bc', env=True).request_uri,
"/init/default/index/a bc")
@@ -368,3 +368,4 @@ if __name__ == '__main__':
setUpModule() # pre-2.7
unittest.main()
tearDownModule()
+
diff --git a/gluon/tests/test_storage.py b/gluon/tests/test_storage.py
index 330ea766..6d35860b 100644
--- a/gluon/tests/test_storage.py
+++ b/gluon/tests/test_storage.py
@@ -21,38 +21,38 @@ class TestStorage(unittest.TestCase):
""" Tests Storage attribute handling """
s = Storage(a=1)
-
+
self.assertEqual(s.a, 1)
self.assertEqual(s['a'], 1)
self.assertEqual(s.b, None)
-
+
s.b = 2
self.assertEqual(s.a, 1)
self.assertEqual(s['a'], 1)
self.assertEqual(s.b, 2)
self.assertEqual(s['b'], 2)
-
+
s['c'] = 3
self.assertEqual(s.c, 3)
self.assertEqual(s['c'], 3)
s.d = list()
self.assertTrue(s.d is s['d'])
-
-
+
+
def test_store_none(self):
""" Test Storage store-None handling
s.key = None deletes an item
s['key'] = None sets the item to None
"""
-
+
s = Storage(a=1)
-
+
self.assertTrue('a' in s)
self.assertFalse('b' in s)
s.a = None
# self.assertFalse('a' in s) # how about this?
-
+
s.a = 1
self.assertTrue('a' in s)
s['a'] = None
@@ -62,12 +62,12 @@ class TestStorage(unittest.TestCase):
def test_item(self):
""" Tests Storage item handling """
-
+
s = Storage()
-
+
self.assertEqual(s.d, None)
self.assertEqual(s['d'], None)
- #self.assertRaises(KeyError, lambda x: s[x], 'd') # old Storage
+ #self.assertRaises(KeyError, lambda x: s[x], 'd') # old Storage
s.a = 1
s['a'] = None
self.assertEquals(s.a, None)
@@ -76,3 +76,4 @@ class TestStorage(unittest.TestCase):
if __name__ == '__main__':
unittest.main()
+
diff --git a/gluon/tests/test_template.py b/gluon/tests/test_template.py
index 810c9876..bba6cded 100644
--- a/gluon/tests/test_template.py
+++ b/gluon/tests/test_template.py
@@ -58,3 +58,4 @@ class TestVirtualFields(unittest.TestCase):
if __name__ == '__main__':
unittest.main()
+
diff --git a/gluon/tests/test_utils.py b/gluon/tests/test_utils.py
index 97d4d2b2..7c0465dc 100644
--- a/gluon/tests/test_utils.py
+++ b/gluon/tests/test_utils.py
@@ -25,3 +25,4 @@ class TestUtils(unittest.TestCase):
if __name__ == '__main__':
unittest.main()
+
diff --git a/gluon/tests/test_web.py b/gluon/tests/test_web.py
new file mode 100644
index 00000000..54295c88
--- /dev/null
+++ b/gluon/tests/test_web.py
@@ -0,0 +1,51 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+ Unit tests for running web2py
+"""
+import sys
+import os
+if os.path.isdir('gluon'):
+ sys.path.append(os.path.realpath('gluon'))
+else:
+ sys.path.append(os.path.realpath('../'))
+
+import unittest
+from gluon.contrib.webclient import WebClient
+
+class TestWeb(unittest.TestCase):
+ def testWebClient(self):
+ client = WebClient('http://127.0.0.1:8000/welcome/default/')
+
+ client.get('index')
+
+ # register
+ data = dict(first_name = 'Homer',
+ last_name = 'Simpson',
+ email = 'homer@web2py.com',
+ password = 'test',
+ password_two = 'test',
+ _formname = 'register')
+ client.post('user/register',data = data)
+
+ # logout
+ client.get('user/logout')
+
+ # login again
+ data = dict(email='homer@web2py.com',
+ password='test',
+ _formname = 'login')
+ client.post('user/login',data = data)
+
+ # check registration and login were successful
+ client.get('index')
+ self.assertTrue('Welcome Homer' in client.text)
+
+ client = WebClient('http://127.0.0.1:8000/admin/default/')
+ client.post('index',data=dict(password='hello'))
+ client.get('site')
+ client.get('design/welcome')
+
+if __name__ == '__main__':
+ unittest.main()
+
diff --git a/gluon/tools.py b/gluon/tools.py
index 58ab9617..ecbc37e2 100644
--- a/gluon/tools.py
+++ b/gluon/tools.py
@@ -1060,6 +1060,7 @@ class Auth(object):
request = current.request
session = current.session
auth = session.auth
+ self.use_username = None # None means postpone detection
self.user_groups = auth and auth.user_groups or {}
if auth and auth.last_visit and auth.last_visit + \
datetime.timedelta(days=0, seconds=auth.expiration) > request.now:
@@ -1257,6 +1258,9 @@ class Auth(object):
if not 'register' in self.settings.actions_disabled:
bar.insert(-1, s2)
bar.insert(-1, register)
+ if self.use_username is None:
+ # should always be false if auth.define_tables() is called
+ self.use_username = 'username' in self.table_user().fields
if self.use_username and \
not 'retrieve_username' in self.settings.actions_disabled:
bar.insert(-1, s2)
@@ -1612,12 +1616,12 @@ class Auth(object):
checks = []
# make a guess about who this user is
for fieldname in ['registration_id','username','email']:
- if fieldname in table_user.fields() and keys.get(fieldname,None):
+ if fieldname in table_user.fields() and \
+ keys.get(fieldname,None):
checks.append(fieldname)
value = keys[fieldname]
- user = user or table_user._db(
- (table_user.registration_id==value)|
- (table_user[fieldname]==value)).select().first()
+ user = table_user(**{fieldname:value})
+ if user: break
if not checks:
return None
if not 'registration_id' in keys:
diff --git a/gluon/validators.py b/gluon/validators.py
index eebf54ef..8edf83cb 100644
--- a/gluon/validators.py
+++ b/gluon/validators.py
@@ -1376,6 +1376,7 @@ class IS_GENERIC_URL(Validator):
"""
+
def __init__(
self,
error_message='enter a valid URL',
@@ -1402,6 +1403,9 @@ class IS_GENERIC_URL(Validator):
"prepend_scheme='%s' is not in allowed_schemes=%s" \
% (self.prepend_scheme, self.allowed_schemes)
+ GENERIC_URL = re.compile(r"%[^0-9A-Fa-f]{2}|%[^0-9A-Fa-f][0-9A-Fa-f]|%[0-9A-Fa-f][^0-9A-Fa-f]|%$|%[0-9A-Fa-f]$|%[^0-9A-Fa-f]$")
+ GENERIC_URL_VALID = re.compile(r"[A-Za-z0-9;/?:@&=+$,\-_\.!~*'\(\)%#]+$")
+
def __call__(self, value):
"""
:param value: a string, the URL to validate
@@ -1411,12 +1415,9 @@ class IS_GENERIC_URL(Validator):
"""
try:
# if the URL does not misuse the '%' character
- if not re.compile(
- r"%[^0-9A-Fa-f]{2}|%[^0-9A-Fa-f][0-9A-Fa-f]|%[0-9A-Fa-f][^0-9A-Fa-f]|%$|%[0-9A-Fa-f]$|%[^0-9A-Fa-f]$"
- ).search(value):
+ if not self.GENERIC_URL.search(value):
# if the URL is only composed of valid characters
- if re.compile(
- r"[A-Za-z0-9;/?:@&=+$,\-_\.!~*'\(\)%#]+$").match(value):
+ if self.GENERIC_URL_VALID.match(value):
# Then split up the URL into its components and check on
# the scheme
scheme = url_split_regex.match(value).group(2)
@@ -1432,11 +1433,10 @@ class IS_GENERIC_URL(Validator):
# ports, check to see if adding a valid scheme fixes
# the problem (but only do this if it doesn't have
# one already!)
- if not re.compile('://').search(value) and None\
- in self.allowed_schemes:
+ if value.find('://')<0 and None in self.allowed_schemes:
schemeToUse = self.prepend_scheme or 'http'
- prependTest = self.__call__(schemeToUse
- + '://' + value)
+ prependTest = self.__call__(
+ schemeToUse + '://' + value)
# if the prepend test succeeded
if prependTest[1] is None:
# if prepending in the output is enabled
@@ -1791,6 +1791,9 @@ class IS_HTTP_URL(Validator):
"""
+ GENERIC_VALID_IP = re.compile("([\w.!~*'|;:&=+$,-]+@)?\d+\.\d+\.\d+\.\d+(:\d*)*$")
+ GENERIC_VALID_DOMAIN = re.compile("([\w.!~*'|;:&=+$,-]+@)?(([A-Za-z0-9]+[A-Za-z0-9\-]*[A-Za-z0-9]+\.)*([A-Za-z0-9]+\.)*)*([A-Za-z]+[A-Za-z0-9\-]*[A-Za-z0-9]+)\.?(:\d*)*$")
+
def __init__(
self,
error_message='enter a valid URL',
@@ -1843,16 +1846,12 @@ class IS_HTTP_URL(Validator):
# if there is an authority component
if authority:
# if authority is a valid IP address
- if re.compile(
- "([\w.!~*'|;:&=+$,-]+@)?\d+\.\d+\.\d+\.\d+(:\d*)*$").match(authority):
+ if self.GENERIC_VALID_IP.match(authority):
# Then this HTTP URL is valid
return (value, None)
else:
# else if authority is a valid domain name
- domainMatch = \
- re.compile(
- "([\w.!~*'|;:&=+$,-]+@)?(([A-Za-z0-9]+[A-Za-z0-9\-]*[A-Za-z0-9]+\.)*([A-Za-z0-9]+\.)*)*([A-Za-z]+[A-Za-z0-9\-]*[A-Za-z0-9]+)\.?(:\d*)*$"
- ).match(authority)
+ domainMatch = self.GENERIC_VALID_DOMAIN.match(authority)
if domainMatch:
# if the top-level domain really exists
if domainMatch.group(5).lower()\
@@ -1865,13 +1864,13 @@ class IS_HTTP_URL(Validator):
path = componentsMatch.group(5)
# relative case: if this is a valid path (if it starts with
# a slash)
- if re.compile('/').match(path):
+ if path.startswith('/'):
# Then this HTTP URL is valid
return (value, None)
else:
# abbreviated case: if we haven't already, prepend a
# scheme and see if it fixes the problem
- if not re.compile('://').search(value):
+ if value.find('://')<0:
schemeToUse = self.prepend_scheme or 'http'
prependTest = self.__call__(schemeToUse
+ '://' + value)
@@ -2521,9 +2520,11 @@ class CLEANUP(Validator):
removes special characters on validation
"""
+ REGEX_CLEANUP = re.compile('[^\x09\x0a\x0d\x20-\x7e]')
- def __init__(self, regex='[^\x09\x0a\x0d\x20-\x7e]'):
- self.regex = re.compile(regex)
+ def __init__(self, regex=None):
+ self.regex = self.REGEX_CLEANUP if regex is None \
+ else re.compile(regex)
def __call__(self, value):
v = self.regex.sub('',str(value).strip())
@@ -2790,11 +2791,13 @@ class IS_STRONG(object):
class IS_IN_SUBSET(IS_IN_SET):
+ REGEX_W = re.compile('\w+')
+
def __init__(self, *a, **b):
IS_IN_SET.__init__(self, *a, **b)
def __call__(self, value):
- values = re.compile("\w+").findall(str(value))
+ values = self.REGEX_W.findall(str(value))
failures = [x for x in values if IS_IN_SET.__call__(self, x)[1]]
if failures:
return (value, translate(self.error_message))
diff --git a/gluon/widget.py b/gluon/widget.py
index 13114466..f995df3d 100644
--- a/gluon/widget.py
+++ b/gluon/widget.py
@@ -33,8 +33,9 @@ try:
import Tkinter, tkMessageBox
import contrib.taskbar_widget
from winservice import web2py_windows_service_handler
+ have_winservice = True
except:
- pass
+ have_winservice = False
try:
@@ -1003,9 +1004,9 @@ def start(cron=True):
print ProgramAuthor
print ProgramVersion
- from dal import drivers
+ from dal import DRIVERS
if not options.nobanner:
- print 'Database drivers available: %s' % ', '.join(drivers)
+ print 'Database drivers available: %s' % ', '.join(DRIVERS)
# ## if -L load options from options.config file
@@ -1080,8 +1081,12 @@ def start(cron=True):
# ## if -W install/start/stop web2py as service
if options.winservice:
if os.name == 'nt':
- web2py_windows_service_handler(['', options.winservice],
- options.config)
+ if have_winservice:
+ web2py_windows_service_handler(['', options.winservice],
+ options.config)
+ else:
+ print 'Error: Missing python module winservice'
+ sys.exit(1)
else:
print 'Error: Windows services not supported on this platform'
sys.exit(1)
diff --git a/logging.example.conf b/logging.example.conf
index ac365aa1..892e242d 100644
--- a/logging.example.conf
+++ b/logging.example.conf
@@ -27,12 +27,14 @@
# set by the setLevel call, the [logger_myapp] section, and the [handler_...]
# section. For example, you will not see DEBUG messages unless all three are
# set to DEBUG.
+#
+# Available levels: DEBUG INFO WARNING ERROR CRITICAL
[loggers]
keys=root,rocket,markdown,web2py,rewrite,cron,app,welcome
[handlers]
-keys=consoleHandler,messageBoxHandler
+keys=consoleHandler,messageBoxHandler,rotatingFileHandler
#keys=consoleHandler,rotatingFileHandler
#keys=osxSysLogHandler
@@ -41,50 +43,53 @@ keys=simpleFormatter
[logger_root]
level=WARNING
-handlers=consoleHandler
+handlers=consoleHandler,rotatingFileHandler
[logger_web2py]
level=WARNING
-handlers=consoleHandler
+handlers=consoleHandler,rotatingFileHandler
qualname=web2py
propagate=0
+# URL rewrite logging (routes.py)
+# See also the logging parameter in routes.py
+#
[logger_rewrite]
level=WARNING
qualname=web2py.rewrite
-handlers=consoleHandler
+handlers=consoleHandler,rotatingFileHandler
propagate=0
[logger_cron]
level=WARNING
qualname=web2py.cron
-handlers=consoleHandler
+handlers=consoleHandler,rotatingFileHandler
propagate=0
# generic app handler
[logger_app]
level=WARNING
qualname=web2py.app
-handlers=consoleHandler
+handlers=consoleHandler,rotatingFileHandler
propagate=0
# welcome app handler
[logger_welcome]
level=WARNING
-qualname=web2py.app.welcome
+qualname=web2py.app.welcome,rotatingFileHandler
handlers=consoleHandler
propagate=0
# loggers for legacy getLogger calls: Rocket and markdown
[logger_rocket]
level=WARNING
-handlers=consoleHandler,messageBoxHandler
+handlers=consoleHandler,messageBoxHandler,rotatingFileHandler
qualname=Rocket
propagate=0
[logger_markdown]
level=WARNING
-handlers=consoleHandler
+handlers=consoleHandler,rotatingFileHandler
qualname=markdown
propagate=0
@@ -106,7 +111,7 @@ args=()
#
[handler_rotatingFileHandler]
class=handlers.RotatingFileHandler
-level=INFO
+level=DEBUG
formatter=simpleFormatter
args=("logs/web2py.log", "a", 1000000, 5)
diff --git a/scripts/tickets2email.py b/scripts/tickets2email.py
index ec4a7120..2a557282 100755
--- a/scripts/tickets2email.py
+++ b/scripts/tickets2email.py
@@ -26,10 +26,8 @@ administrator_email = 'you@localhost'
while 1:
for file in os.listdir(path):
- filename = os.path.join(path, file)
-
if not ALLOW_DUPLICATES:
- fileobj = open(filename, 'r')
+ fileobj = open(file, 'r')
try:
file_data = fileobj.read()
finally:
@@ -42,10 +40,10 @@ while 1:
hashes[key] = 1
error = RestrictedError()
- error.load(request, request.application, filename)
+ error.load(request, request.application, file)
mail.send(to=administrator_email, subject='new web2py ticket', message=error.traceback)
- os.unlink(filename)
+ os.unlink(os.path.join(path, file))
time.sleep(SLEEP_MINUTES * 60)
diff --git a/web2py.py b/web2py.py
index fbd402e9..26425777 100755
--- a/web2py.py
+++ b/web2py.py
@@ -9,10 +9,10 @@ if '__file__' in globals():
elif hasattr(sys, 'frozen'):
path = os.path.dirname(os.path.abspath(sys.executable)) # for py2exe
else: #should never happen
- path = os.getcwd()
+ path = os.getcwd()
os.chdir(path)
-sys.path = [path]+[p for p in sys.path if not p==path]
+sys.path = [path]+[p for p in sys.path if not p == path]
# import gluon.import_all ##### This should be uncommented for py2exe.py
import gluon.widget