Merge branch 'master' of git://github.com/web2py/web2py

Conflicts:
	applications/admin/languages/pt.py
	gluon/dal.py
This commit is contained in:
Leonel Câmara
2014-07-29 20:19:26 +01:00
320 changed files with 32287 additions and 11755 deletions
+1 -1
View File
@@ -11,7 +11,7 @@ Web2Py framework modules
"""
__all__ = ['A', 'B', 'BEAUTIFY', 'BODY', 'BR', 'CAT', 'CENTER', 'CLEANUP', 'CODE', 'CRYPT', 'DAL', 'DIV', 'EM', 'EMBED', 'FIELDSET', 'FORM', 'Field', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'HEAD', 'HR', 'HTML', 'HTTP', 'I', 'IFRAME', 'IMG', 'INPUT', 'IS_ALPHANUMERIC', 'IS_DATE', 'IS_DATETIME', 'IS_DATETIME_IN_RANGE', 'IS_DATE_IN_RANGE', 'IS_DECIMAL_IN_RANGE', 'IS_EMAIL', 'IS_LIST_OF_EMAILS', 'IS_EMPTY_OR', 'IS_EQUAL_TO', 'IS_EXPR', 'IS_FLOAT_IN_RANGE', 'IS_IMAGE', 'IS_JSON', 'IS_INT_IN_RANGE', 'IS_IN_DB', 'IS_IN_SET', 'IS_IPV4', 'IS_LENGTH', 'IS_LIST_OF', 'IS_LOWER', 'IS_MATCH', 'IS_NOT_EMPTY', 'IS_NOT_IN_DB', 'IS_NULL_OR', 'IS_SLUG', 'IS_STRONG', 'IS_TIME', 'IS_UPLOAD_FILENAME', 'IS_UPPER', 'IS_URL', 'LABEL', 'LEGEND', 'LI', 'LINK', 'LOAD', 'MARKMIN', 'MENU', 'META', 'OBJECT', 'OL', 'ON', 'OPTGROUP', 'OPTION', 'P', 'PRE', 'SCRIPT', 'SELECT', 'SPAN', 'SQLFORM', 'SQLTABLE', 'STRONG', 'STYLE', 'TABLE', 'TAG', 'TBODY', 'TD', 'TEXTAREA', 'TFOOT', 'TH', 'THEAD', 'TITLE', 'TR', 'TT', 'UL', 'URL', 'XHTML', 'XML', 'redirect', 'current', 'embed64']
from globals import current
from html import *
from validators import *
+111 -146
View File
@@ -1,10 +1,13 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
This file is part of the web2py Web Framework
Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu>
License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
| This file is part of the web2py Web Framework
| Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu>
| License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
Utility functions for the Admin application
===========================================
-------------------------------------------
"""
import os
import sys
@@ -25,15 +28,11 @@ if not global_settings.web2py_runtime_gae:
def apath(path='', r=None):
"""
Builds a path inside an application folder
"""Builds a path inside an application folder
Parameters
----------
path:
path within the application folder
r:
the global request object
Args:
path(str): path within the application folder
r: the global request object
"""
@@ -44,20 +43,14 @@ def apath(path='', r=None):
def app_pack(app, request, raise_ex=False, filenames=None):
"""
Builds a w2p package for the application
"""Builds a w2p package for the application
Parameters
----------
app:
application name
request:
the global request object
Returns
-------
filename:
Args:
app(str): application name
request: the global request object
Returns:
filename of the w2p file or None on error
"""
try:
if filenames is None: app_cleanup(app, request)
@@ -71,20 +64,15 @@ def app_pack(app, request, raise_ex=False, filenames=None):
def app_pack_compiled(app, request, raise_ex=False):
"""
Builds a w2p bytecode-compiled package for the application
"""Builds a w2p bytecode-compiled package for the application
Parameters
----------
app:
application name
request:
the global request object
Args:
app(str): application name
request: the global request object
Returns
-------
filename:
Returns:
filename of the w2p file or None on error
"""
try:
@@ -98,15 +86,15 @@ def app_pack_compiled(app, request, raise_ex=False):
def app_cleanup(app, request):
"""
Removes session, cache and error files
"""Removes session, cache and error files
Args:
app(str): application name
request: the global request object
Returns:
True if everything went ok, False otherwise
Parameters
----------
app:
application name
request:
the global request object
"""
r = True
@@ -140,15 +128,15 @@ def app_cleanup(app, request):
def app_compile(app, request):
"""
Compiles the application
"""Compiles the application
Args:
app(str): application name
request: the global request object
Returns:
None if everything went ok, traceback text if errors are found
Parameters
----------
app:
application name
request:
the global request object
"""
from compileapp import compile_application, remove_compiled_application
folder = apath(app, request)
@@ -162,15 +150,11 @@ def app_compile(app, request):
def app_create(app, request, force=False, key=None, info=False):
"""
Create a copy of welcome.w2p (scaffolding) app
"""Create a copy of welcome.w2p (scaffolding) app
Parameters
----------
app:
application name
request:
the global request object
Args:
app(str): application name
request: the global request object
"""
path = apath(app, request)
@@ -215,28 +199,23 @@ def app_create(app, request, force=False, key=None, info=False):
def app_install(app, fobj, request, filename, overwrite=None):
"""
Installs an application:
"""Installs an application:
- Identifies file type by filename
- Writes `fobj` contents to the `../deposit/` folder
- Calls `w2p_unpack()` to do the job.
Parameters
----------
app:
new application name
fobj:
file object containing the application to be installed
request:
the global request object
filename:
original filename of the `fobj`, required to determine extension
Args:
app(str): new application name
fobj(obj): file object containing the application to be installed
request: the global request object
filename(str): original filename of the `fobj`,
required to determine extension
overwrite(bool): force overwrite of existing application
Returns
-------
upname:
Returns:
name of the file where app is temporarily stored or `None` on failure
"""
did_mkdir = False
if filename[-4:] == '.w2p':
@@ -265,19 +244,15 @@ def app_install(app, fobj, request, filename, overwrite=None):
def app_uninstall(app, request):
"""
Uninstalls the application.
"""Uninstalls the application.
Parameters
----------
app:
application name
request:
the global request object
Args:
app(str): application name
request: the global request object
Returns:
`True` on success, `False` on failure
Returns
-------
`True` on success, `False` on failure
"""
try:
# Hey App, this is your end...
@@ -289,22 +264,16 @@ def app_uninstall(app, request):
def plugin_pack(app, plugin_name, request):
"""
Builds a w2p package for the application
"""Builds a w2p package for the plugin
Parameters
----------
app:
application name
plugin_name:
the name of the plugin without plugin_ prefix
request:
the current request app
Args:
app(str): application name
plugin_name(str): the name of the plugin without `plugin_` prefix
request: the current request app
Returns:
filename of the w2p file or False on error
Returns
-------
filename:
filename of the w2p file or None on error
"""
try:
filename = apath(
@@ -316,30 +285,24 @@ def plugin_pack(app, plugin_name, request):
def plugin_install(app, fobj, request, filename):
"""
Installs an application:
"""Installs a plugin:
- Identifies file type by filename
- Writes `fobj` contents to the `../deposit/` folder
- Calls `w2p_unpack()` to do the job.
- Calls `w2p_unpack_plugin()` to do the job.
Parameters
----------
app:
new application name
fobj:
file object containing the application to be installed
request:
the global request object
filename:
original filename of the `fobj`, required to determine extension
Args:
app(str): new application name
fobj: file object containing the application to be installed
request: the global request object
filename: original filename of the `fobj`,
required to determine extension
Returns:
name of the file where plugin is temporarily stored
or `False` on failure
Returns
-------
upname:
name of the file where app is temporarily stored or `None` on failure
"""
upname = apath('../deposit/%s' % filename, request)
try:
@@ -354,23 +317,20 @@ def plugin_install(app, fobj, request, filename):
def check_new_version(myversion, version_url):
"""
Compares current web2py's version with the latest stable web2py version.
"""Compares current web2py's version with the latest stable web2py version.
Parameters
----------
myversion:
the current version as stored in file `web2py/VERSION`
version_URL:
the URL that contains the version of the latest stable release
Args:
myversion: the current version as stored in file `web2py/VERSION`
version_URL: the URL that contains the version
of the latest stable release
Returns:
tuple: state, version
- state : `True` if upgrade available, `False` if current
version is up-to-date, -1 on error
- version : the most up-to-version available
Returns
-------
state:
`True` if upgrade available, `False` if current version if up-to-date,
-1 on error
version:
the most up-to-version available
"""
try:
from urllib import urlopen
@@ -389,9 +349,13 @@ def check_new_version(myversion, version_url):
def unzip(filename, dir, subfolder=''):
"""
Unzips filename into dir (.zip only, no .gz etc)
if subfolder!='' it unzip only files in subfolder
"""Unzips filename into dir (.zip only, no .gz etc)
Args:
filename(str): archive
dir(str): destination
subfolder(str): if != '' unzips only files in subfolder
"""
filename = abspath(filename)
if not zipfile.is_zipfile(filename):
@@ -413,21 +377,22 @@ def unzip(filename, dir, subfolder=''):
def upgrade(request, url='http://web2py.com'):
"""
Upgrades web2py (src, osx, win) is a new version is posted.
"""Upgrades web2py (src, osx, win) if a new version is posted.
It detects whether src, osx or win is running and downloads the right one
Parameters
----------
request:
the current request object, required to determine version and path
url:
the incomplete url where to locate the latest web2py
actual url is url+'/examples/static/web2py_(src|osx|win).zip'
Args:
request: the current request object
(required to determine version and path)
url: the incomplete url where to locate the latest web2py
(actual url is url+'/examples/static/web2py_(src|osx|win).zip')
Returns
-------
True on success, False on failure (network problem or old version)
tuple: completed, traceback
- completed: True on success, False on failure
(network problem or old version)
- traceback: None on success, raised exception details on failure
"""
web2py_version = request.env.web2py_version
gluon_parent = request.env.gluon_parent
+101 -96
View File
@@ -2,12 +2,12 @@
# -*- coding: utf-8 -*-
"""
This file is part of the web2py Web Framework
Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu>
License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
| This file is part of the web2py Web Framework
| Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu>
| License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
Basic caching classes and methods
=================================
---------------------------------
- Cache - The generic caching object interfacing with the others
- CacheInRam - providing caching in ram
@@ -42,57 +42,56 @@ __all__ = ['Cache', 'lazy_cache']
DEFAULT_TIME_EXPIRE = 300
class CacheAbstract(object):
"""
Abstract class for cache implementations.
Main function is now to provide referenced api documentation.
Main function just provides referenced api documentation.
Use CacheInRam or CacheOnDisk instead which are derived from this class.
Attentions, Michele says:
There are signatures inside gdbm files that are used directly
by the python gdbm adapter that often are lagging behind in the
detection code in python part.
On every occasion that a gdbm store is probed by the python adapter,
the probe fails, because gdbm file version is newer.
Using gdbm directly from C would work, because there is backward
compatibility, but not from python!
The .shelve file is discarded and a new one created (with new
signature) and it works until it is probed again...
The possible consequences are memory leaks and broken sessions.
Note:
Michele says: there are signatures inside gdbm files that are used
directly by the python gdbm adapter that often are lagging behind in the
detection code in python part.
On every occasion that a gdbm store is probed by the python adapter,
the probe fails, because gdbm file version is newer.
Using gdbm directly from C would work, because there is backward
compatibility, but not from python!
The .shelve file is discarded and a new one created (with new
signature) and it works until it is probed again...
The possible consequences are memory leaks and broken sessions.
"""
cache_stats_name = 'web2py_cache_statistics'
def __init__(self, request=None):
"""
Parameters
----------
request:
the global request object
"""Initializes the object
Args:
request: the global request object
"""
raise NotImplementedError
def __call__(self, key, f,
time_expire=DEFAULT_TIME_EXPIRE):
"""
Tries retrieve the value corresponding to `key` from the cache of the
object exists and if it did not expire, else it called the function `f`
and stores the output in the cache corresponding to `key`. In the case
the output of the function is returned.
Tries to retrieve the value corresponding to `key` from the cache if the
object exists and if it did not expire, else it calls the function `f`
and stores the output in the cache corresponding to `key`. It always
returns the function that is returned.
:param key: the key of the object to be store or retrieved
:param f: the function, whose output is to be cached
:param time_expire: expiration of the cache in microseconds
Args:
key(str): the key of the object to be stored or retrieved
f(function): the function whose output is to be cached.
- `time_expire` is used to compare the current time with the time when
the requested object was last saved in cache. It does not affect
future requests.
- Setting `time_expire` to 0 or negative value forces the cache to
refresh.
If `f` is `None` the cache is cleared.
time_expire(int): expiration of the cache in seconds.
If the function `f` is `None` the cache is cleared.
It's used to compare the current time with the time
when the requested object was last saved in cache. It does not
affect future requests. Setting `time_expire` to 0 or negative
value forces the cache to refresh.
"""
raise NotImplementedError
@@ -101,11 +100,9 @@ class CacheAbstract(object):
Clears the cache of all keys that match the provided regular expression.
If no regular expression is provided, it clears all entries in cache.
Parameters
----------
regex:
if provided, only keys matching the regex will be cleared.
Otherwise all keys are cleared.
Args:
regex: if provided, only keys matching the regex will be cleared,
otherwise all keys are cleared.
"""
raise NotImplementedError
@@ -114,12 +111,9 @@ class CacheAbstract(object):
"""
Increments the cached value for the given key by the amount in value
Parameters
----------
key:
key for the cached object to be incremeneted
value:
amount of the increment (defaults to 1, can be negative)
Args:
key(str): key for the cached object to be incremeneted
value(int): amount of the increment (defaults to 1, can be negative)
"""
raise NotImplementedError
@@ -131,6 +125,7 @@ class CacheAbstract(object):
for (key, value) in storage.items():
if r.match(str(key)):
del storage[key]
break
class CacheInRam(CacheAbstract):
@@ -187,12 +182,17 @@ class CacheInRam(CacheAbstract):
time_expire=DEFAULT_TIME_EXPIRE,
destroyer=None):
"""
Attention! cache.ram does not copy the cached object. It just stores a reference to it.
Turns out the deepcopying the object has some problems:
1) would break backward compatibility
2) would be limiting because people may want to cache live objects
3) would work unless we deepcopy no storage and retrival which would make things slow.
Anyway. You can deepcopy explicitly in the function generating the value to be cached.
Attention! cache.ram does not copy the cached object.
It just stores a reference to it. Turns out the deepcopying the object
has some problems:
- would break backward compatibility
- would be limiting because people may want to cache live objects
- would work unless we deepcopy no storage and retrival which would make
things slow.
Anyway. You can deepcopy explicitly in the function generating the value
to be cached.
"""
self.initialize()
@@ -419,10 +419,8 @@ class Cache(object):
def __init__(self, request):
"""
Parameters
----------
request:
the global request object
Args:
request: the global request object
"""
# GAE will have a special caching
if have_settings and settings.global_settings.web2py_runtime_gae:
@@ -444,24 +442,30 @@ class Cache(object):
prefix=None, session=False, vars=True, lang=True,
user_agent=False, public=True, valid_statuses=None,
quick=None):
"""
Experimental!
"""Better fit for caching an action
Warning:
Experimental!
Currently only HTTP 1.1 compliant
reference : http://code.google.com/p/doctype-mirror/wiki/ArticleHttpCaching
time_expire: same as @cache
cache_model: same as @cache
prefix: add a prefix to the calculated key
session: adds response.session_id to the key
vars: adds request.env.query_string
lang: adds T.accepted_language
user_agent: if True, adds is_mobile and is_tablet to the key.
Pass a dict to use all the needed values (uses str(.items())) (e.g. user_agent=request.user_agent())
used only if session is not True
public: if False forces the Cache-Control to be 'private'
valid_statuses: by default only status codes starting with 1,2,3 will be cached.
pass an explicit list of statuses on which turn the cache on
quick: Session,Vars,Lang,User-agent,Public:
fast overrides with initial strings, e.g. 'SVLP' or 'VLP', or 'VLP'
Args:
time_expire(int): same as @cache
cache_model(str): same as @cache
prefix(str): add a prefix to the calculated key
session(bool): adds response.session_id to the key
vars(bool): adds request.env.query_string
lang(bool): adds T.accepted_language
user_agent(bool or dict): if True, adds is_mobile and is_tablet to the key.
Pass a dict to use all the needed values (uses str(.items()))
(e.g. user_agent=request.user_agent()). Used only if session is
not True
public(bool): if False forces the Cache-Control to be 'private'
valid_statuses: by default only status codes starting with 1,2,3 will be cached.
pass an explicit list of statuses on which turn the cache on
quick: Session,Vars,Lang,User-agent,Public:
fast overrides with initials, e.g. 'SVLP' or 'VLP', or 'VLP'
"""
from gluon import current
from gluon.http import HTTP
@@ -561,33 +565,32 @@ class Cache(object):
"""
Decorator function that can be used to cache any function/method.
Example::
Args:
key(str) : the key of the object to be store or retrieved
time_expire(int) : expiration of the cache in seconds
`time_expire` is used to compare the current time with the time
when the requested object was last saved in cache.
It does not affect future requests.
Setting `time_expire` to 0 or negative value forces the cache to
refresh.
cache_model(str): can be "ram", "disk" or other (like "memcache").
Defaults to "ram"
@cache('key', 5000, cache.ram)
def f():
return time.ctime()
When the function f is called, web2py tries to retrieve
the value corresponding to `key` from the cache of the
When the function `f` is called, web2py tries to retrieve
the value corresponding to `key` from the cache if the
object exists and if it did not expire, else it calles the function `f`
and stores the output in the cache corresponding to `key`. In the case
the output of the function is returned.
:param key: the key of the object to be store or retrieved
:param time_expire: expiration of the cache in microseconds
:param cache_model: "ram", "disk", or other
(like "memcache" if defined). It defaults to "ram".
Example: ::
Notes
-----
`time_expire` is used to compare the curret time with the time when the
requested object was last saved in cache. It does not affect future
requests.
Setting `time_expire` to 0 or negative value forces the cache to
refresh.
@cache('key', 5000, cache.ram)
def f():
return time.ctime()
If the function `f` is an action, we suggest using
@cache.client instead
Note:
If the function `f` is an action, we suggest using
@cache.action instead
"""
def tmp(func, cache=self, cache_model=cache_model):
@@ -606,11 +609,13 @@ class Cache(object):
def lazy_cache(key=None, time_expire=None, cache_model='ram'):
"""
can be used to cache any function including in modules,
Can be used to cache any function including ones in modules,
as long as the cached function is only called within a web2py request
if a key is not provided, one is generated from the function name
the time_expire defaults to None (no cache expiration)
if cache_model is "ram" then the model is current.cache.ram, etc.
If a key is not provided, one is generated from the function name
`time_expire` defaults to None (no cache expiration)
If cache_model is "ram" then the model is current.cache.ram, etc.
"""
def decorator(f, key=key, time_expire=time_expire, cache_model=cache_model):
key = key or repr(f)
+12 -11
View File
@@ -2,19 +2,19 @@
# -*- coding: utf-8 -*-
"""
This file is part of the web2py Web Framework
Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu>
License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
| This file is part of the web2py Web Framework
| Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu>
| License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
Functions required to execute app components
============================================
--------------------------------------------
FOR INTERNAL USE ONLY
Note:
FOR INTERNAL USE ONLY
"""
from os import stat
import thread
import logging
from gluon.fileutils import read_file
cfs = {} # for speed-up
@@ -26,11 +26,12 @@ def getcfs(key, filename, filter=None):
Caches the *filtered* file `filename` with `key` until the file is
modified.
:param key: the cache key
:param filename: the file to cache
:param filter: is the function used for filtering. Normally `filename` is a
.py file and `filter` is a function that bytecode compiles the file.
In this way the bytecode compiled file is cached. (Default = None)
Args:
key(str): the cache key
filename: the file to cache
filter: is the function used for filtering. Normally `filename` is a
.py file and `filter` is a function that bytecode compiles the file.
In this way the bytecode compiled file is cached. (Default = None)
This is used on Google App Engine since pyc files cannot be saved.
"""
+40 -23
View File
@@ -2,14 +2,15 @@
# -*- coding: utf-8 -*-
"""
This file is part of the web2py Web Framework
Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu>
License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
| This file is part of the web2py Web Framework
| Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu>
| License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
Functions required to execute app components
============================================
---------------------------------------------
FOR INTERNAL USE ONLY
Note:
FOR INTERNAL USE ONLY
"""
import re
@@ -22,7 +23,7 @@ from gluon.storage import Storage, List
from gluon.template import parse_template
from gluon.restricted import restricted, compile2
from gluon.fileutils import mktree, listdir, read_file, write_file
from gluon.myregex import regex_expose
from gluon.myregex import regex_expose, regex_longcomments
from gluon.languages import translator
from gluon.dal import BaseAdapter, SQLDB, SQLField, DAL, Field
from gluon.sqlhtml import SQLFORM, SQLTABLE
@@ -125,16 +126,27 @@ def LOAD(c=None, f='index', args=None, vars=None,
extension=None, target=None, ajax=False, ajax_trap=False,
url=None, user_signature=False, timeout=None, times=1,
content='loading...', **attr):
""" LOAD a component into the action's document
""" LOADs a component into the action's document
Timing options:
-times: An integer or string ("infinity"/"continuous")
specifies how many times the component is requested
-timeout (milliseconds): specifies the time to wait before
starting the request or the frequency if times is greater than
1 or "infinity".
Timing options default to the normal behavior. The component
is added on page loading without delay.
Args:
c(str): controller
f(str): function
args(tuple or list): arguments
vars(dict): vars
extension(str): extension
target(str): id of the target
ajax(bool): True to enable AJAX bahaviour
ajax_trap(bool): True if `ajax` is set to `True`, traps
both links and forms "inside" the target
url(str): overrides `c`,`f`,`args` and `vars`
user_signature(bool): adds hmac signature to all links
with a key that is different for every user
timeout(int): in milliseconds, specifies the time to wait before
starting the request or the frequency if times is greater than
1 or "infinity"
times(integer or str): how many times the component will be requested
"infinity" or "continuous" are accepted to reload indefinitely the
component
"""
from html import TAG, DIV, URL, SCRIPT, XML
if args is None:
@@ -199,6 +211,7 @@ def LOAD(c=None, f='index', args=None, vars=None,
request.env.path_info
other_request.cid = target
other_request.env.http_web2py_component_element = target
other_request.restful = request.restful # Needed when you call LOAD() on a controller who has some actions decorates with @request.restful()
other_response.view = '%s/%s.%s' % (c, f, other_request.extension)
other_environment = copy.copy(current.globalenv) # NASTY
@@ -440,7 +453,8 @@ def read_pyc(filename):
Read the code inside a bytecode compiled file if the MAGIC number is
compatible
:returns: a code object
Returns:
a code object
"""
data = read_file(filename, 'rb')
if not is_gae and data[:4] != imp.get_magic():
@@ -481,6 +495,9 @@ def compile_models(folder):
save_pyc(filename)
os.unlink(filename)
def find_exposed_functions(data):
data = regex_longcomments.sub('',data)
return regex_expose.findall(data)
def compile_controllers(folder):
"""
@@ -491,7 +508,7 @@ def compile_controllers(folder):
for fname in listdir(path, '.+\.py$'):
### why is this here? save_pyc(pjoin(path, file))
data = read_file(pjoin(path, fname))
exposed = regex_expose.findall(data)
exposed = find_exposed_functions(data)
for function in exposed:
command = data + "\nresponse._vars=response._caller(%s)\n" % \
function
@@ -515,7 +532,7 @@ def run_models_in(environment):
folder = environment['request'].folder
c = environment['request'].controller
f = environment['request'].function
#f = environment['request'].function
response = environment['response']
path = pjoin(folder, 'models')
@@ -525,10 +542,10 @@ def run_models_in(environment):
models = sorted(listdir(cpath, '^models[_.][\w.]+\.pyc$', 0), model_cmp)
else:
models = sorted(listdir(path, '^\w+\.py$', 0, sort=False), model_cmp_sep)
models_to_run = None
models_to_run = None
for model in models:
if response.models_to_run != models_to_run:
regex = models_to_run = response.models_to_run
regex = models_to_run = response.models_to_run[:]
if isinstance(regex, list):
regex = re_compile('|'.join(regex))
if models_to_run:
@@ -537,7 +554,7 @@ def run_models_in(environment):
fname = model[n:-4].replace('.','/')+'.py'
else:
n = len(path)+1
fname = model[n:].replace(os.path.sep,'/')
fname = model[n:].replace(os.path.sep,'/')
if not regex.search(fname) and c != 'appadmin':
continue
elif compiled:
@@ -570,7 +587,7 @@ def run_controller_in(controller, function, environment):
filename = pjoin(path, 'controllers_%s_%s.pyc'
% (controller, function))
### end for backward compatibility
if not os.path.exists(filename):
if not os.path.exists(filename):
raise HTTP(404,
rewrite.THREAD_LOCAL.routes.error_message % badf,
web2py_error=badf)
@@ -602,7 +619,7 @@ def run_controller_in(controller, function, environment):
rewrite.THREAD_LOCAL.routes.error_message % badc,
web2py_error=badc)
code = read_file(filename)
exposed = regex_expose.findall(code)
exposed = find_exposed_functions(code)
if not function in exposed:
raise HTTP(404,
rewrite.THREAD_LOCAL.routes.error_message % badf,
+11 -11
View File
@@ -2,22 +2,22 @@
# -*- coding: utf-8 -*-
"""
This file is part of the web2py Web Framework
Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu>
License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
| This file is part of the web2py Web Framework
| Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu>
| License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
CONTENT_TYPE dictionary created against freedesktop.org' shared mime info
CONTENT_TYPE dictionary created against freedesktop.org's shared mime info
database version 1.1.
Deviations from official standards:
- '.md': 'application/x-genesis-rom' --> 'text/x-markdown'
- '.png': 'image/x-apple-ios-png' --> 'image/png'
- .md: application/x-genesis-rom --> text/x-markdown
- .png: image/x-apple-ios-png --> image/png
Additions:
- '.load': 'text/html'
- '.json': 'application/json'
- '.jsonp': 'application/jsonp'
- '.pickle': 'application/python-pickle'
- '.w2p': 'application/w2p'
- .load: text/html
- .json: application/json
- .jsonp: application/jsonp
- .pickle: application/python-pickle
- .w2p': application/w2p
"""
__all__ = ['contenttype']
+1 -1
View File
@@ -3,7 +3,7 @@ AIM class to credit card payment with authorize.net
Fork of authnet code written by John Conde
http://www.johnconde.net/blog/integrate-the-authorizenet-aim-api-with-python-3-2/
Unknown license, assuming public domain
BSDv3 License
Modifed by Massimo Di Pierro
+1 -1
View File
@@ -3,7 +3,7 @@ DowCommerce class to process credit card payments with DowCommerce.com
Modifications to support Dow Commerce API from code originally written by John Conde
http://www.johnconde.net/blog/integrate-the-authorizenet-aim-api-with-python-3-2/
Unknown license, assuming public domain
BSDv3 License
Modifed by Dave Stoll dave.stoll@gmail.com
+2
View File
@@ -159,6 +159,8 @@ def extension(url):
def expand_one(url, cdict):
# try ombed but first check in cache
if '@' in url and not '://'in url:
return '<a href="mailto:%s">%s</a>' % (url, url)
if cdict and url in cdict:
r = cdict[url]
else:
+12 -9
View File
@@ -17,25 +17,28 @@ class MemcacheClient(object):
client = Client()
def __init__(self, request):
def __init__(self, request, default_time_expire = 300):
self.request = request
self.default_time_expire = default_time_expire
def initialize(self):
pass
def __call__(
self,
key,
f,
time_expire=300,
time_expire=None,
):
if time_expire is None:
time_expire = self.default_time_expire
key = '%s/%s' % (self.request.application, key)
dt = time_expire
value = None
obj = self.client.get(key)
if obj and (dt is None or obj[0] > time.time() - dt):
obj = self.client.get(key) if time_expire != 0 else None
if obj:
value = obj[1]
elif f is None:
if obj:
self.client.delete(key)
else:
elif f is not None:
value = f()
self.client.set(key, (time.time(), value), time=time_expire)
return value
+9 -1
View File
@@ -58,8 +58,16 @@ def pyfpdf_from_html(html):
pass
pdf = MyFPDF()
pdf.add_page()
# pyfpdf needs some attributes to render the table correctly:
html = sanitize(
html, escape=False) # should have better list of allowed tags
html, allowed_attributes={
'a': ['href', 'title'],
'img': ['src', 'alt'],
'blockquote': ['type'],
'td': ['align', 'bgcolor', 'colspan', 'height', 'width'],
'tr': ['bgcolor', 'height', 'width'],
'table': ['border', 'bgcolor', 'height', 'width'],
}, escape=False)
pdf.write_html(html, image_map=image_map)
return XML(pdf.output(dest='S'))
+341
View File
@@ -0,0 +1,341 @@
import json
from collections import OrderedDict
from gluon import URL, IS_SLUG
# compliant with https://github.com/collection-json/spec
# also compliant with http://code.ge/media-types/collection-next-json/
"""
Example controller:
def api():
from gluon.contrib.hypermedia import Collection
policies = {
'thing': {
'GET':{'query':None,'fields':['id', 'name']},
'POST':{'query':None,'fields':['name']},
'PUT':{'query':None,'fields':['name']},
'DELETE':{'query':None},
},
'attr': {
'GET':{'query':None,'fields':['id', 'name', 'thing']},
'POST':{'query':None,'fields':['name', 'thing']},
'PUT':{'query':None,'fields':['name', 'thing']},
'DELETE':{'query':None},
},
}
return Collection(db).process(request,response,policies)
"""
__all__ = ['Collection']
class Collection(object):
VERSION = '1.0'
MAXITEMS = 100
def __init__(self,db, extensions=True, compact=False):
self.db = db
self.extensions = extensions
self.compact = compact
def row2data(self,table,row,text=False):
""" converts a DAL Row object into a collection.item """
data = []
if self.compact:
for fieldname in (self.table_policy.get('fields',table.fields)):
field = table[fieldname]
if not ((field.type=='text' and text==False) or
field.type=='blob' or
field.type.startswith('reference ') or
field.type.startswith('list:reference ')) and field.name in row:
data.append(row[field.name])
else:
for fieldname in (self.table_policy.get('fields',table.fields)):
field = table[fieldname]
if not ((field.type=='text' and text==False) or
field.type=='blob' or
field.type.startswith('reference ') or
field.type.startswith('list:reference ')) and field.name in row:
data.append({'name':field.name,'value':row[field.name],
'prompt':field.label, 'type':field.type})
return data
def row2links(self,table,row):
""" converts a DAL Row object into a set of links referencing the row """
links = []
for field in table._referenced_by:
if field._tablename in self.policies:
if row:
href = URL(args=field._tablename,vars={field.name:row.id},scheme=True)
else:
href = URL(args=field._tablename,scheme=True)+'?%s={id}' % field.name
links.append({'rel':'current','href':href,'prompt':str(field),
'type':'children'})
if row:
fields = self.table_policy.get('fields', table.fields)
for fieldname in fields:
field = table[fieldname]
if field.type.startswith('reference '):
href = URL(args=field.type[10:],vars={'id':row[fieldname]},
scheme=True)
links.append({'rel':'current','href':href,'prompt':str(field),
'type':'parent'})
for fieldname in fields:
field = table[fieldname]
if field.type=='upload' and row[fieldname]:
href = URL('download',args=row[fieldname],scheme=True)
links.append({'rel':'current','href':href,'prompt':str(field),
'type':'attachment'})
# should this be supported?
for rel,build in (self.table_policy.get('links',{}).items()):
links.append({'rel':'current','href':build(row),'prompt':rel})
# not sure
return links
def table2template(self,table):
""" confeverts a table into its form template """
data = []
fields = self.table_policy.get('fields', table.fields)
for fieldname in fields:
field = table[fieldname]
info = {'name': field.name, 'value': '', 'prompt': field.label}
policies = self.policies[table._tablename]
# https://github.com/collection-json/extensions/blob/master/template-validation.md
info['type'] = str(field.type) # FIX THIS
if hasattr(field,'regexp_validator'):
info['regexp'] = field.regexp_validator
info['required'] = field.required
info['post_writable'] = field.name in policies['POST'].get('fields',fields)
info['put_writable'] = field.name in policies['PUT'].get('fields',fields)
info['options'] = {} # FIX THIS
data.append(info)
return {'data':data}
def request2query(self,table,vars):
""" parses a request and converts it into a query """
if len(self.request.args)>1:
vars.id = self.request.args[1]
fieldnames = table.fields
queries = [table]
limitby = [0,self.MAXITEMS+1]
orderby = 'id'
for key,value in vars.items():
if key=='_offset':
limitby[0] = int(value) # MAY FAIL
elif key == '_limit':
limitby[1] = int(value)+1 # MAY FAIL
elif key=='_orderby':
orderby = value
elif key in fieldnames:
queries.append(table[key] == value)
elif key.endswith('.eq') and key[:-3] in fieldnames: # for completeness (useless)
queries.append(table[key[:-3]] == value)
elif key.endswith('.lt') and key[:-3] in fieldnames:
queries.append(table[key[:-3]] < value)
elif key.endswith('.le') and key[:-3] in fieldnames:
queries.append(table[key[:-3]] <= value)
elif key.endswith('.gt') and key[:-3] in fieldnames:
queries.append(table[key[:-3]] > value)
elif key.endswith('.ge') and key[:-3] in fieldnames:
queries.append(table[key[:-3]] >= value)
elif key.endswith('.contains') and key[:-9] in fieldnames:
queries.append(table[key[:-9]].contains(value))
elif key.endswith('.startswith') and key[:-11] in fieldnames:
queries.append(table[key[:-11]].startswith(value))
elif key.endswith('.ne') and key[:-3] in fieldnames:
queries.append(table[key][:-3] != value)
else:
raise ValueError("Invalid Query")
filter_query = self.table_policy.get('query')
if filter_query:
queries.append(filter_query)
query = reduce(lambda a,b:a&b,queries[1:]) if len(queries)>1 else queries[0]
orderby = [table[f] if f[0]!='~' else ~table[f[1:]] for f in orderby.split(',')]
return (query, limitby, orderby)
def table2queries(self,table, href):
""" generates a set of collection.queries examples for the table """
data = []
for fieldname in (self.table_policy.get('fields', table.fields)):
data.append({'name':fieldname,'value':''})
if self.extensions:
data.append({'name':fieldname+'.ne','value':''}) # NEW !!!
data.append({'name':fieldname+'.lt','value':''})
data.append({'name':fieldname+'.le','value':''})
data.append({'name':fieldname+'.gt','value':''})
data.append({'name':fieldname+'.ge','value':''})
if table[fieldname].type in ['string','text']:
data.append({'name':fieldname+'.contains','value':''})
data.append({'name':fieldname+'.startswith','value':''})
data.append({'name':'_limitby','value':''})
data.append({'name':'_offset','value':''})
data.append({'name':'_orderby','value':''})
return [{'rel' : 'search', 'href' : href, 'prompt' : 'Search', 'data' : data}]
def process(self,request,response,policies=None):
""" the main method, processes a request, filters by policies and produces a JSON response """
self.request = request
self.response = response
self.policies = policies
db = self.db
tablename = request.args(0)
r = OrderedDict()
r['version'] = self.VERSION
tablenames = policies.keys() if policies else db.tables
# if there is no tables
if not tablename:
r['href'] = URL(scheme=True),
# https://github.com/collection-json/extensions/blob/master/model.md
r['links'] = [{'rel' : t, 'href' : URL(args=t,scheme=True), 'model':t}
for t in tablenames]
response.headers['Content-Type'] = 'application/vnd.collection+json'
return response.json({'collection':r})
# or if the tablenames is invalid
if not tablename in tablenames:
return self.error(400,'BAD REQUEST','Invalid table name')
# of if the method is invalid
if not request.env.request_method in policies[tablename]:
return self.error(400,'BAD REQUEST','Method not recognized')
# get the policies
self.table_policy = policies[tablename][request.env.request_method]
# process GET
if request.env.request_method=='GET':
table = db[tablename]
r['href'] = URL(args=tablename)
r['items'] = items = []
try:
(query, limitby, orderby) = self.request2query(table,request.get_vars)
fields = [table[fn] for fn in (self.table_policy.get('fields', table.fields))]
fields = filter(lambda field: field.readable, fields)
rows = db(query).select(*fields,**dict(limitby=limitby, orderby=orderby))
except:
db.rollback()
return self.error(400,'BAD REQUEST','Invalid Query')
r['items_found'] = db(query).count()
delta = limitby[1]-limitby[0]-1
r['links'] = self.row2links(table,None) if self.compact else []
text = r['items_found']<2
for row in rows[:delta]:
id = row.id
for name in ('slug','fullname','title','name'):
if name in row:
href = URL(args=(tablename,id,IS_SLUG.urlify(row[name] or '')),
scheme=True)
break
else:
href = URL(args=(tablename,id),scheme=True)
if self.compact:
items.append(self.row2data(table,row,text))
else:
items.append({
'href':href,
'data':self.row2data(table,row,text),
'links':self.row2links(table,row)
});
if self.extensions and len(rows)>delta:
vars = dict(request.get_vars)
vars['_offset'] = limitby[1]-1
vars['_limit'] = limitby[1]-1+delta
r['next'] = {'rel':'next',
'href':URL(args=request.args,vars=vars,scheme=True)}
if self.extensions and limitby[0]>0:
vars = dict(request.get_vars)
vars['_offset'] = max(0,limitby[0]-delta)
vars['_limit'] = limitby[0]
r['previous'] = {'rel':'previous',
'href':URL(args=request.args,vars=vars,scheme=True)}
data = []
if not self.compact:
r['queries'] = self.table2queries(table, r['href'])
r['template'] = self.table2template(table)
response.headers['Content-Type'] = 'application/vnd.collection+json'
return response.json({'collection':r})
# process DELETE
elif request.env.request_method=='DELETE':
table = db[tablename]
if not request.get_vars:
return self.error(400, "BAD REQUEST", "Nothing to delete")
else:
try:
(query, limitby, orderby) = self.request2query(table, request.vars)
n = db(query).delete() # MAY FAIL
response.status = 204
return ''
except:
db.rollback()
return self.error(400,'BAD REQUEST','Invalid Query')
return response.json(r)
# process POST and PUT (on equal footing!)
elif request.env.request_method in ('POST','PUT'): # we treat them the same!
table = db[tablename]
if 'json' in request.env.content_type:
data = request.post_vars.data
if request.get_vars or len(request.args)>1: # update
# ADD validate fields and return error
try:
(query, limitby, orderby) = self.request2query(table, request.get_vars)
fields = filter(lambda (fn,value):table[fn].writable,data.items())
res = db(query).validate_and_update(**dict(fields)) # MAY FAIL
if res.errors:
return self.error(400,'BAD REQUEST','Validation Error',res.errors)
else:
response.status = 200
return ''
except:
db.rollback()
return self.error(400,'BAD REQUEST','Invalid Query')
else: # create
# ADD validate fields and return error
try:
fields = filter(lambda (fn,value):table[fn].writable,data.items())
res = table.validate_and_insert(**dict(fields)) # MAY FAIL
if res.errors:
return self.error(400,'BAD REQUEST','Validation Error',res.errors)
else:
response.status = 201
response.headers['location'] = \
URL(args=(tablename,res.id),scheme=True)
return ''
except SyntaxError,e: #Exception,e:
db.rollback()
return self.error(400,'BAD REQUEST','Invalid Query:'+e)
def error(self,code="400", title="BAD REQUEST", message="UNKNOWN", form_errors={}):
request, response = self.request, self.response
r = OrderedDict({
"version" : self.VERSION,
"href" : URL(args=request.args,vars=request.vars),
"error" : {
"title" : title,
"code" : code,
"message" : message}})
if self.extensions and form_errors:
# https://github.com/collection-json/extensions/blob/master/errors.md
r['errors'] = errors = {}
for key, value in form_errors.items():
errors[key] = {'title':'Validation Error','code':'','message':value}
response.headers['Content-Type'] = 'application/vnd.collection+json'
response.status = 400
return response.json({'collection':r})
example_policies = {
'thing': {
'GET':{'query':None,'fields':['id', 'name']},
'POST':{'query':None,'fields':['name']},
'PUT':{'query':None,'fields':['name']},
'DELETE':{'query':None},
},
'attr': {
'GET':{'query':None,'fields':['id', 'name', 'thing']},
'POST':{'query':None,'fields':['name', 'thing']},
'PUT':{'query':None,'fields':['name', 'thing']},
'DELETE':{'query':None},
},
}
+1 -1
View File
@@ -4,7 +4,7 @@
"""
This file is part of web2py Web Framework (Copyrighted, 2007-2009).
Developed by Massimo Di Pierro <mdipierro@cs.depaul.edu>.
License: GPL v2
License: LGPL v3
Tinkered by Szabolcs Gyuris < szimszo n @ o regpreshaz dot eu>
"""
@@ -4,7 +4,7 @@
"""
This file is part of web2py Web Framework (Copyrighted, 2007-2009).
Developed by Massimo Di Pierro <mdipierro@cs.depaul.edu>.
License: GPL v2
License: LGPL v3
Thanks to Hans Donner <hans.donner@pobox.com> for GaeGoogleAccount.
"""
@@ -0,0 +1,138 @@
#!/usr/bin/env python
# coding: utf8
"""
RPX Authentication for web2py
Developed by Nathan Freeze (Copyright © 2009)
Email <nathan@freezable.com>
Modified by Massimo Di Pierro
This file contains code to allow using RPXNow.com (now Jainrain.com)
services with web2py
"""
import os
import re
import urllib
from gluon import *
from gluon.tools import fetch
from gluon.storage import Storage
import gluon.contrib.simplejson as json
class RPXAccount(object):
"""
from gluon.contrib.login_methods.rpx_account import RPXAccount
auth.settings.actions_disabled=['register','change_password',
'request_reset_password']
auth.settings.login_form = RPXAccount(request,
api_key="...",
domain="...",
url = "http://localhost:8000/%s/default/user/login" % request.application)
"""
def __init__(self,
request,
api_key="",
domain="",
url="",
embed=True,
auth_url="https://rpxnow.com/api/v2/auth_info",
language="en",
prompt='rpx',
on_login_failure=None,
):
self.request = request
self.api_key = api_key
self.embed = embed
self.auth_url = auth_url
self.domain = domain
self.token_url = url
self.language = language
self.profile = None
self.prompt = prompt
self.on_login_failure = on_login_failure
self.mappings = Storage()
dn = {'givenName': '', 'familyName': ''}
self.mappings.Facebook = lambda profile, dn=dn:\
dict(registration_id=profile.get("identifier", ""),
username=profile.get("preferredUsername", ""),
email=profile.get("email", ""),
first_name=profile.get("name", dn).get("givenName", ""),
last_name=profile.get("name", dn).get("familyName", ""))
self.mappings.Google = lambda profile, dn=dn:\
dict(registration_id=profile.get("identifier", ""),
username=profile.get("preferredUsername", ""),
email=profile.get("email", ""),
first_name=profile.get("name", dn).get("givenName", ""),
last_name=profile.get("name", dn).get("familyName", ""))
self.mappings.default = lambda profile:\
dict(registration_id=profile.get("identifier", ""),
username=profile.get("preferredUsername", ""),
email=profile.get("email", ""),
first_name=profile.get("preferredUsername", ""),
last_name='')
def get_user(self):
request = self.request
if request.vars.token:
user = Storage()
data = urllib.urlencode(
dict(apiKey=self.api_key, token=request.vars.token))
auth_info_json = fetch(self.auth_url + '?' + data)
auth_info = json.loads(auth_info_json)
if auth_info['stat'] == 'ok':
self.profile = auth_info['profile']
provider = re.sub('[^\w\-]', '', self.profile['providerName'])
user = self.mappings.get(
provider, self.mappings.default)(self.profile)
return user
elif self.on_login_failure:
redirect(self.on_login_failure)
return None
def login_form(self):
request = self.request
args = request.args
rpxform = """
<script type="text/javascript">
(function() {
if (typeof window.janrain !== 'object') window.janrain = {};
if (typeof window.janrain.settings !== 'object') window.janrain.settings = {};
janrain.settings.tokenUrl = '%s';
function isReady() { janrain.ready = true; };
if (document.addEventListener) {
document.addEventListener("DOMContentLoaded", isReady, false);
} else {
window.attachEvent('onload', isReady);
}
var e = document.createElement('script');
e.type = 'text/javascript';
e.id = 'janrainAuthWidget';
if (document.location.protocol === 'https:') {
e.src = 'https://rpxnow.com/js/lib/%s/engage.js';
} else {
e.src = 'http://widget-cdn.rpxnow.com/js/lib/%s/engage.js';
}
var s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(e, s);
})();
</script>
<div id="janrainEngageEmbed"></div>""" % (self.token_url, self.domain, self.domain)
return XML(rpxform)
def use_janrain(auth, filename='private/janrain.key', **kwargs):
path = os.path.join(current.request.folder, filename)
if os.path.exists(path):
request = current.request
domain, key = open(path, 'r').read().strip().split(':')
host = current.request.env.http_host
url = URL('default', 'user', args='login', scheme=True)
auth.settings.actions_disabled = \
['register', 'change_password', 'request_reset_password']
auth.settings.login_form = RPXAccount(
request, api_key=key, domain=domain, url=url, **kwargs)
+22 -7
View File
@@ -16,7 +16,9 @@ except Exception, e:
def ldap_auth(server='ldap', port=None,
base_dn='ou=users,dc=domain,dc=com',
mode='uid', secure=False, cert_path=None, cert_file=None,
mode='uid', secure=False,
cert_path=None, cert_file=None,
cacert_path=None, cacert_file=None, key_file=None,
bind_dn=None, bind_pw=None, filterstr='objectClass=*',
username_attrib='uid',
custom_scope='subtree',
@@ -170,6 +172,9 @@ def ldap_auth(server='ldap', port=None,
secure=secure,
cert_path=cert_path,
cert_file=cert_file,
cacert_file=cacert_file,
cert_file=cert_file,
key_file=key_file,
filterstr=filterstr,
username_attrib=username_attrib,
custom_scope=custom_scope,
@@ -568,7 +573,10 @@ def ldap_auth(server='ldap', port=None,
ldap_mode=mode,
secure=secure,
cert_path=cert_path,
cert_file=cert_file):
cert_file=cert_file,
cacert_file=cacert_file,
cert_file=cert_file,
key_file=key_file):
"""
Inicialize ldap connection
"""
@@ -576,12 +584,19 @@ def ldap_auth(server='ldap', port=None,
if secure:
if not ldap_port:
ldap_port = 636
con = ldap.initialize(
"ldaps://" + ldap_server + ":" + str(ldap_port))
if cert_path:
con.set_option(ldap.OPT_X_TLS_CACERTDIR, cert_path)
if cacert_path:
ldap.set_option(ldap.OPT_X_TLS_CACERTDIR, cacert_path)
if cacert_file:
ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER)
ldap.set_option(ldap.OPT_X_TLS_CACERTFILE, cacert_file)
if cert_file:
con.set_option(ldap.OPT_X_TLS_CACERTFILE, cert_file)
ldap.set_option(ldap.OPT_X_TLS_CERTFILE, cert_file)
if key_file:
ldap.set_option(ldap.OPT_X_TLS_KEYFILE, key_file)
con = ldap.initialize("ldaps://" + ldap_server + ":" + str(ldap_port))
else:
if not ldap_port:
ldap_port = 389
@@ -30,7 +30,7 @@ class LoginRadiusAccount(object):
"""
def __init__(self, request, api_key="", api_secret="",
url=None, on_login_failure=None):
url="", on_login_failure=None):
self.request = request
self.api_key = api_key
@@ -78,8 +78,8 @@ class LoginRadiusAccount(object):
LoginRadius_SocialLogin.util.ready(function () {
$ui = LoginRadius_SocialLogin.lr_login_settings;
$ui.interfacesize = "";$ui.apikey = "%s";
$ui.callback=""; $ui.lrinterfacecontainer ="interfacecontainerdiv";
LoginRadius_SocialLogin.init(options); });""" % self.api_key)
$ui.callback="%s"; $ui.lrinterfacecontainer ="interfacecontainerdiv";
LoginRadius_SocialLogin.init(options); });""" % (self.api_key, self.url))
form = DIV(container, loginradius_lib, widget)
return form
@@ -3,7 +3,7 @@
"""
Written by Michele Comitini <mcm@glisco.it>
License: GPL v3
License: LGPL v3
Adds support for OAuth1.0a authentication to web2py.
@@ -172,9 +172,9 @@ server for requests. It can be used for the optional"scope" parameters for Face
if open_url:
try:
data = open_url.read()
resp_type = open_url.info().get('Content-Type')
resp_type = open_url.info().gettype()
# try json style first
if not resp_type or resp_type == 'application/json':
if not resp_type or resp_type[:16] == 'application/json':
try:
tokendata = json.loads(data)
current.session.token = tokendata
+1 -1
View File
@@ -106,7 +106,7 @@ class RPXAccount(object):
self.domain, self.token_url, self.language),
_scrolling="no",
_frameborder="no",
_style="width:400px;height:240px;")
_style="width:400px;height:240px;")
else:
JANRAIN_URL = \
"https://%s.rpxnow.com/openid/v2/signin?token_url=%s"
+1 -1
View File
@@ -3,7 +3,7 @@
"""
Written by Michele Comitini <mcm@glisco.it>
License: GPL v3
License: LGPL v3
Adds support for x509 authentication.
+7 -2
View File
@@ -551,7 +551,7 @@ regex_list=re.compile('^(?:(?:(#{1,6})|(?:(\.+|\++|\-+)(\.)?))\s*)?(.*)$')
regex_bq_headline=re.compile('^(?:(\.+|\++|\-+)(\.)?\s+)?(-{3}-*)$')
regex_tq=re.compile('^(-{3}-*)(?::(?P<c>[a-zA-Z][_a-zA-Z\-\d]*)(?:\[(?P<p>[a-zA-Z][_a-zA-Z\-\d]*)\])?)?$')
regex_proto = re.compile(r'(?<!["\w>/=])(?P<p>\w+):(?P<k>\w+://[\w\d\-+=?%&/:.]+)', re.M)
regex_auto = re.compile(r'(?<!["\w>/=])(?P<k>\w+://[\w\d\-+_=?%&/:.,;#]+\w)',re.M)
regex_auto = re.compile(r'(?<!["\w>/=])(?P<k>\w+://[\w\d\-+_=?%&/:.,;#]+\w|[\w\-.]+@[\w\-.]+)',re.M)
regex_link=re.compile(r'('+LINK+r')|\[\[(?P<s>.+?)\]\]',re.S)
regex_link_level2=re.compile(r'^(?P<t>\S.*?)?(?:\s+\[(?P<a>.+?)\])?(?:\s+(?P<k>\S+))?(?:\s+(?P<p>popup))?\s*$',re.S)
regex_media_level2=re.compile(r'^(?P<t>\S.*?)?(?:\s+\[(?P<a>.+?)\])?(?:\s+(?P<k>\S+))?\s+(?P<p>img|IMG|left|right|center|video|audio|blockleft|blockright)(?:\s+(?P<w>\d+px))?\s*$',re.S)
@@ -648,7 +648,9 @@ def autolinks_simple(url):
image, video or audio tag
"""
u_url=url.lower()
if u_url.endswith(('.jpg','.jpeg','.gif','.png')):
if '@' in url and not '://' in url:
return '<a href="mailto:%s">%s</a>' % (url, url)
elif u_url.endswith(('.jpg','.jpeg','.gif','.png')):
return '<img src="%s" controls />' % url
elif u_url.endswith(('.mp4','.mpeg','.mov','.ogv')):
return '<video src="%s" controls></video>' % url
@@ -673,6 +675,9 @@ def protolinks_simple(proto, url):
return '<img style="width:100px" src="http://chart.apis.google.com/chart?cht=qr&chs=100x100&chl=%s&choe=UTF-8&chld=H" alt="QR Code" title="QR Code" />'%url
return proto+':'+url
def email_simple(email):
return '<a href="mailto:%s">%s</a>' % (email, email)
def render(text,
extra={},
allowed={},
+5 -7
View File
@@ -15,16 +15,14 @@ regex_dd=re.compile('\$\$(?P<latex>.*?)\$\$')
regex_code = re.compile('('+META+')|(``(?P<t>.*?)``(:(?P<c>\w+))?)',re.S)
regex_title = re.compile('^#{1} (?P<t>[^\n]+)',re.M)
regex_maps = [
(re.compile('[ \t\r]+\n'),'\n'),
(re.compile('[ \t\r]+\n'),'\n'),
(re.compile('\*\*(?P<t>[^\s\*]+( +[^\s\*]+)*)\*\*'),'{\\\\bf \g<t>}'),
(re.compile("''(?P<t>[^\s']+( +[^\s']+)*)''"),'{\\it \g<t>}'),
(re.compile('^#{6} (?P<t>[^\n]+)',re.M),'\n\n{\\\\bf \g<t>}\n'),
(re.compile('^#{5} (?P<t>[^\n]+)',re.M),'\n\n{\\\\bf \g<t>}\n'),
(re.compile('^#{4} (?P<t>[^\n]+)',re.M),'\n\n\\\\goodbreak\\subsubsection{\g<t>}\n'),
(re.compile('^#{3} (?P<t>[^\n]+)',re.M),'\n\n\\\\goodbreak\\subsection{\g<t>}\n'),
(re.compile('^#{2} (?P<t>[^\n]+)',re.M),'\n\n\\\\goodbreak\\section{\g<t>}\n'),
(re.compile('^#{1} (?P<t>[^\n]+)',re.M),''),
(re.compile('^#{5,6}\s*(?P<t>[^\n]+)',re.M),'\n\n{\\\\bf \g<t>}\n'),
(re.compile('^#{4}\s*(?P<t>[^\n]+)',re.M),'\n\n\\\\goodbreak\\subsubsection{\g<t>}\n'),
(re.compile('^#{3}\s*(?P<t>[^\n]+)',re.M),'\n\n\\\\goodbreak\\subsection{\g<t>}\n'),
(re.compile('^#{2}\s*(?P<t>[^\n]+)',re.M),'\n\n\\\\goodbreak\\section{\g<t>}\n'),
(re.compile('^#{1}\s*(?P<t>[^\n]+)',re.M),''),
(re.compile('^\- +(?P<t>.*)',re.M),'\\\\begin{itemize}\n\\item \g<t>\n\\end{itemize}'),
(re.compile('^\+ +(?P<t>.*)',re.M),'\\\\begin{itemize}\n\\item \g<t>\n\\end{itemize}'),
(re.compile('\\\\end\{itemize\}\s+\\\\begin\{itemize\}'),'\n'),
+1 -1
View File
@@ -1,6 +1,6 @@
"""
Created by Massimo Di Pierro
Licese BSD
License BSD
"""
import subprocess
+8 -3
View File
@@ -5,7 +5,7 @@
This file is part of web2py Web Framework (Copyrighted, 2007-2009).
Developed by Massimo Di Pierro <mdipierro@cs.depaul.edu> and
Robin B <robi123@gmail.com>.
License: GPL v2
License: LGPLv3
"""
__all__ = ['MEMDB', 'Field']
@@ -292,8 +292,13 @@ class Table(DALStorage):
def __str__(self):
return self._tablename
def __call__(self, id):
return self.get(id)
def __call__(self, id, **kwargs):
record = self.get(id)
if record is None:
return None
if kwargs and any(record[key]!=kwargs[key] for key in kwargs):
return None
return record
class Expression(object):
+255
View File
@@ -0,0 +1,255 @@
# -*- encoding: utf-8 -*-
from imaplib import ParseFlags
# mockimaplib: A very simple mock server module for imap client APIs
# Copyright (C) 2014 Alan Etkin <spametki@gmail.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or(at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this program. If not, see
# <http://www.gnu.org/licenses/lgpl.html>
"""
mockimaplib allows you to test applications connecting to a dummy imap
service. For more details on the api subset implemented,
refer to the imaplib docs.
The client should configure a dictionary to map imap string queries to sets
of entries stored in a message dummy storage dictionary. The module includes
a small set of default message records (SPAM and MESSAGES), two mailboxes
(Draft and INBOX) and a list of query/resultset entries (RESULTS).
Usage:
>>> import mockimaplib
>>> connection = mockimaplib.IMAP4_SSL(<host>)
>>> connection.login(<user>, <password>)
None
>>> connection.select("INBOX")
("OK", ... <mailbox length>)
# fetch commands specifying single uid or message id
# will try to get messages recorded in SPAM
>>> connection.uid(...)
<search query or fetch result>
# returns a string list of matching message ids
>>> connection.search(<query>)
("OK", ... "1 2 ... n")
"""
MESSAGES = (
'MIME-Version: 1.0\r\nReceived: by 10.140.91.199 with HTTP; Mon, 27 Jan 2014 13:52:30 -0800 (PST)\r\nDate: Mon, 27 Jan 2014 19:52:30 -0200\r\nDelivered-To: nurse@example.com\r\nMessage-ID: <10101010101010010000010101010001010101001010010000001@mail.example.com>\r\nSubject: spam1\r\nFrom: Mr. Gumby <gumby@example.com>\r\nTo: The nurse <nurse@example.com>\r\nContent-Type: text/plain; charset=ISO-8859-1\r\n\r\nNurse!\r\n\r\n\r\n',
'MIME-Version: 1.0\r\nReceived: by 10.140.91.199 with HTTP; Mon, 27 Jan 2014 13:52:47 -0800 (PST)\r\nDate: Mon, 27 Jan 2014 19:52:47 -0200\r\nDelivered-To: nurse@example.com\r\nMessage-ID: <101010101010100100000101010100010101010010100100000010@mail.example.com>\r\nSubject: spam2\r\nFrom: Mr. Gumby <gumby@example.com>\r\nTo: The nurse <nurse@example.com>\r\nContent-Type: text/plain; charset=ISO-8859-1\r\n\r\nNurse, nurse!',
'MIME-Version: 1.0\r\nReceived: by 10.140.91.199 with HTTP; Mon, 27 Jan 2014 13:54:54 -0800 (PST)\r\nDate: Mon, 27 Jan 2014 19:54:54 -0200\r\nDelivered-To: nurse@example.com\r\nMessage-ID: <1010101010101001000001010101000101010100101001000000101@mail.example.com>\r\nSubject: spamalot1\r\nFrom: Mr. Gumby <gumby@example.com>\r\nTo: The nurse <nurse@example.com>\r\nContent-Type: text/plain; charset=ISO-8859-1\r\n\r\nNurse!\r\n\r\n\r\n',
'MIME-Version: 1.0\r\n\r\nReceived: by 10.140.91.199 with HTTP; Mon, 27 Jan 2014 13:54:54 -0800 (PST)\r\nDate: Mon, 27 Jan 2014 19:54:54 -0200\r\nDelivered-To: nurse@example.com\r\nMessage-ID: <101010101010100100000101010100010101010010100100000010101@mail.example.com>\r\nSubject: spamalot2\r\nFrom: Mr. Gumby <gumby@example.com>\r\nTo: The nurse <nurse@example.com>\r\nContent-Type: text/plain; charset=ISO-8859-1\r\n\r\nNurse! ... Nurse! ... Nurse!\r\n\r\n\r\n')
SPAM = {
"INBOX": [
{"uid": "483209",
"headers": MESSAGES[0],
"complete": MESSAGES[0],
"flags": ""},
{"uid": "483211",
"headers": MESSAGES[1],
"complete": MESSAGES[1],
"flags": ""},
{"uid": "483225",
"headers": MESSAGES[2],
"complete": MESSAGES[2],
"flags": ""}],
"Draft":[
{"uid": "483432",
"headers": MESSAGES[3],
"complete": MESSAGES[3],
"flags": ""},]
}
RESULTS = {
# <query string>: [<str uid> | <long id>, ...]
"INBOX": {
"(ALL)": (1, 2, 3),
"(1:3)": (1, 2, 3)},
"Draft": {
"(1:1)": (1,)},
}
class Connection(object):
"""Dummy connection object for the imap client.
By default, uses the module SPAM and RESULT
sets (use Connection.setup for custom values)"""
def login(self, user, password):
pass
def __init__(self):
self._readonly = False
self._mailbox = None
self.setup()
def list(self):
return ('OK', ['(\\HasNoChildren) "/" "%s"' % key for key in self.spam])
def select(self, tablename, readonly=False):
self._readonly = readonly
"""args: mailbox, boolean
result[1][0] -> int last message id / mailbox lenght
result[0] = 'OK'
"""
self._mailbox = tablename
return ('OK', (len(SPAM[self._mailbox]), None))
def uid(self, command, uid, arg):
""" args:
command: "search" | "fetch"
uid: None | uid
parts: "(ALL)" | "(RFC822 FLAGS)" | "(RFC822.HEADER FLAGS)"
"search", None, "(ALL)" -> ("OK", ("uid_1 uid_2 ... uid_<mailbox length>", None))
"search", None, "<query>" -> ("OK", ("uid_1 uid_2 ... uid_n", None))
"fetch", uid, parts -> ("OK", (("<id> ...", "<raw message as specified in parts>"), "<flags>")
[0] [1][0][0] [1][0][1] [1][1]
"""
if command == "search":
return self._search(arg)
elif command == "fetch":
return self._fetch(uid, arg)
def _search(self, query):
return ("OK", (" ".join([str(item["uid"]) for item in self._get_messages(query)]), None))
def _fetch(self, value, arg):
try:
message = self.spam[self._mailbox][value - 1]
message_id = value
except TypeError:
for x, item in enumerate(self.spam[self._mailbox]):
if item["uid"] == value:
message = item
message_id = x + 1
break
parts = "headers"
if arg in ("(ALL)", "(RFC822 FLAGS)"):
parts = "complete"
return ("OK", (("%s " % message_id, message[parts]), message["flags"]))
def _get_messages(self, query):
if query.strip().isdigit():
return [self.spam[self._mailbox][int(query.strip()) - 1],]
elif query[1:-1].strip().isdigit():
return [self.spam[self._mailbox][int(query[1:-1].strip()) -1],]
elif query[1:-1].replace("UID", "").strip().isdigit():
for item in self.spam[self._mailbox]:
if item["uid"] == query[1:-1].replace("UID", "").strip():
return [item,]
messages = []
try:
for m in self.results[self._mailbox][query]:
try:
self.spam[self._mailbox][m - 1]["id"] = m
messages.append(self.spam[self._mailbox][m - 1])
except TypeError:
for x, item in enumerate(self.spam[self._mailbox]):
if item["uid"] == m:
item["id"] = x + 1
messages.append(item)
break
except IndexError:
# message removed
pass
return messages
except KeyError:
raise ValueError("The client issued an unexpected query: %s" % query)
def setup(self, spam={}, results={}):
"""adds custom message and query databases or sets
the values to the module defaults.
"""
self.spam = spam
self.results = results
if not spam:
for key in SPAM:
self.spam[key] = []
for d in SPAM[key]:
self.spam[key].append(d.copy())
if not results:
for key in RESULTS:
self.results[key] = RESULTS[key].copy()
def search(self, first, query):
""" args:
first: None
query: string with mailbox query (flags, date, uid, id, ...)
example: '2:15723 BEFORE 27-Jan-2014 FROM "gumby"'
result[1][0] -> "id_1 id_2 ... id_n"
"""
messages = self._get_messages(query)
ids = " ".join([str(item["id"]) for item in messages])
return ("OK", (ids, None))
def append(self, mailbox, flags, struct_time, message):
"""
result, data = self.connection.append(mailbox, flags, struct_time, message)
if result == "OK":
uid = int(re.findall("\d+", str(data))[-1])
"""
last = self.spam[mailbox][-1]
try:
uid = int(last["uid"]) +1
except ValueError:
alluids = []
for _mailbox in self.spam.keys():
for item in self.spam[_mailbox]:
try:
alluids.append(int(item["uid"]))
except:
pass
if len(alluids) > 0:
uid = max(alluids) + 1
else:
uid = 1
flags = "FLAGS " + flags
item = {"uid": str(uid), "headers": message, "complete": message, "flags": flags}
self.spam[mailbox].append(item)
return ("OK", "spam spam %s spam" % uid)
def store(self, *args):
"""
implements some flag commands
args: ("<id>", "<+|->FLAGS", "(\\Flag1 \\Flag2 ... \\Flagn)")
"""
message = self.spam[self._mailbox][int(args[0] - 1)]
old_flags = ParseFlags(message["flags"])
flags = ParseFlags("FLAGS" + args[2])
if args[1].strip().startswith("+"):
message["flags"] = "FLAGS (%s)" % " ".join(set(flags + old_flags))
elif args[1].strip().startswith("-"):
message["flags"] = "FLAGS (%s)" % " ".join([flag for flag in old_flags if not flag in flags])
def expunge(self):
"""implements removal of deleted flag messages"""
for x, item in enumerate(self.spam[self._mailbox]):
if "\\Deleted" in item["flags"]:
self.spam[self._mailbox].pop(x)
class IMAP4(object):
""">>> connection = IMAP4() # creates the dummy imap4 client object"""
def __new__(self, *args, **kwargs):
# args: (server, port)
return Connection()
IMAP4_SSL = IMAP4
+3 -3
View File
@@ -133,6 +133,8 @@ def populate_generator(table, default=True, compute=False, contents={}):
continue
elif field.type == 'id':
continue
elif field.type == 'upload':
continue
elif default and not field.default in (None, ''):
record[fieldname] = field.default
elif compute and field.compute:
@@ -153,8 +155,6 @@ def populate_generator(table, default=True, compute=False, contents={}):
record[fieldname] = datetime.time(h, m, 0)
elif field.type == 'password':
record[fieldname] = ''
elif field.type == 'upload':
record[fieldname] = None
elif field.type == 'integer' and \
hasattr(field.requires, 'options'):
options = field.requires.options(zero=False)
@@ -266,5 +266,5 @@ def populate_generator(table, default=True, compute=False, contents={}):
if __name__ == '__main__':
ell = Learner()
ell.loadd(eval(IUP))
ell.loadd(IUP)
print ell.generate(1000, prefix=None)
+14 -5
View File
@@ -1,7 +1,16 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"PySimpleSOAP"
import client
import server
import simplexml
import transport
"""PySimpleSOAP"""
__author__ = "Mariano Reingart"
__author_email__ = "reingart@gmail.com"
__copyright__ = "Copyright (C) 2013 Mariano Reingart"
__license__ = "LGPL 3.0"
__version__ = "1.11"
TIMEOUT = 60
from . import client, server, simplexml, transport
File diff suppressed because it is too large Load Diff
+489
View File
@@ -0,0 +1,489 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation; either version 3, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
# for more details.
"""Pythonic simple SOAP Client helpers"""
from __future__ import unicode_literals
import sys
if sys.version > '3':
basestring = unicode = str
import datetime
from decimal import Decimal
import os
import logging
import hashlib
import warnings
try:
import urllib2
from urlparse import urlsplit
except ImportError:
from urllib import request as urllib2
from urllib.parse import urlsplit
from . import __author__, __copyright__, __license__, __version__
log = logging.getLogger(__name__)
def fetch(url, http, cache=False, force_download=False, wsdl_basedir=''):
"""Download a document from a URL, save it locally if cache enabled"""
# check / append a valid schema if not given:
url_scheme, netloc, path, query, fragment = urlsplit(url)
if not url_scheme in ('http', 'https', 'file'):
for scheme in ('http', 'https', 'file'):
try:
if not url.startswith("/") and scheme in ('http', 'https'):
tmp_url = "%s://%s" % (scheme, os.path.join(wsdl_basedir, url))
else:
tmp_url = "%s:%s" % (scheme, os.path.join(wsdl_basedir, url))
log.debug('Scheme not found, trying %s' % scheme)
return fetch(tmp_url, http, cache, force_download, wsdl_basedir)
except Exception as e:
log.error(e)
raise RuntimeError('No scheme given for url: %s' % url)
# make md5 hash of the url for caching...
filename = '%s.xml' % hashlib.md5(url.encode('utf8')).hexdigest()
if isinstance(cache, basestring):
filename = os.path.join(cache, filename)
if cache and os.path.exists(filename) and not force_download:
log.info('Reading file %s' % filename)
f = open(filename, 'r')
xml = f.read()
f.close()
else:
if url_scheme == 'file':
log.info('Fetching url %s using urllib2' % url)
f = urllib2.urlopen(url)
xml = f.read()
else:
log.info('GET %s using %s' % (url, http._wrapper_version))
response, xml = http.request(url, 'GET', None, {})
if cache:
log.info('Writing file %s' % filename)
if not os.path.isdir(cache):
os.makedirs(cache)
f = open(filename, 'w')
f.write(xml)
f.close()
return xml
def sort_dict(od, d):
"""Sort parameters (same order as xsd:sequence)"""
if isinstance(od, dict):
ret = OrderedDict()
for k in od.keys():
v = d.get(k)
# don't append null tags!
if v is not None:
if isinstance(v, dict):
v = sort_dict(od[k], v)
elif isinstance(v, list):
v = [sort_dict(od[k][0], v1) for v1 in v]
ret[k] = v
if hasattr(od, 'namespace'):
ret.namespace = od.namespace
ret.qualified = od.qualified
return ret
else:
return d
def make_key(element_name, element_type, namespace):
"""Return a suitable key for elements"""
# only distinguish 'element' vs other types
if element_type in ('complexType', 'simpleType'):
eltype = 'complexType'
else:
eltype = element_type
if eltype not in ('element', 'complexType', 'simpleType'):
raise RuntimeError("Unknown element type %s = %s" % (element_name, eltype))
return (element_name, eltype, namespace)
def process_element(elements, element_name, node, element_type, xsd_uri, dialect, namespace, qualified=None,
soapenc_uri = 'http://schemas.xmlsoap.org/soap/encoding/'):
"""Parse and define simple element types"""
log.debug('Processing element %s %s' % (element_name, element_type))
for tag in node:
if tag.get_local_name() in ('annotation', 'documentation'):
continue
elif tag.get_local_name() in ('element', 'restriction'):
log.debug('%s has no children! %s' % (element_name, tag))
children = tag # element "alias"?
alias = True
elif tag.children():
children = tag.children()
alias = False
else:
log.debug('%s has no children! %s' % (element_name, tag))
continue # TODO: abstract?
d = OrderedDict()
d.namespace = namespace
d.qualified = qualified
for e in children:
t = e['type']
if not t:
t = e['base'] # complexContent (extension)!
if not t:
t = e['ref'] # reference to another element
if not t:
# "anonymous" elements had no type attribute but children
if e['name'] and e.children():
# create a type name to process the children
t = "%s_%s" % (element_name, e['name'])
c = e.children()
et = c.get_local_name()
c = c.children()
process_element(elements, t, c, et, xsd_uri, dialect, namespace, qualified)
else:
t = 'anyType' # no type given!
t = t.split(":")
if len(t) > 1:
ns, type_name = t
else:
ns, type_name = None, t[0]
if element_name == type_name and not alias and len(children) > 1:
continue # abort to prevent infinite recursion
uri = ns and e.get_namespace_uri(ns) or xsd_uri
if uri in (xsd_uri, soapenc_uri) and type_name != 'Array':
# look for the type, None == any
fn = REVERSE_TYPE_MAP.get(type_name, None)
elif uri == soapenc_uri and type_name == 'Array':
# arrays of simple types (look at the attribute tags):
fn = []
for a in e.children():
for k, v in a[:]:
if k.endswith(":arrayType"):
type_name = v
if ":" in type_name:
type_name = type_name[type_name.index(":")+1:]
if "[]" in type_name:
type_name = type_name[:type_name.index("[]")]
fn.append(REVERSE_TYPE_MAP.get(type_name, None))
else:
fn = None
if not fn:
# simple / complex type, postprocess later
if ns:
fn_namespace = uri # use the specified namespace
else:
fn_namespace = namespace # use parent namespace (default)
for k, v in e[:]:
if k.startswith("xmlns:"):
# get the namespace uri from the element
fn_namespace = v
fn = elements.setdefault(make_key(type_name, 'complexType', fn_namespace), OrderedDict())
if e['maxOccurs'] == 'unbounded' or (uri == soapenc_uri and type_name == 'Array'):
# it's an array... TODO: compound arrays? and check ns uri!
if isinstance(fn, OrderedDict):
if len(children) > 1 and dialect in ('jetty',):
# Jetty style support
# {'ClassName': [{'attr1': val1, 'attr2': val2}]
fn.array = True
else:
# .NET style support (backward compatibility)
# [{'ClassName': {'attr1': val1, 'attr2': val2}]
d.array = True
else:
if dialect in ('jetty',):
# scalar support [{'attr1': [val1]}]
fn = [fn]
else:
d.array = True
if (e['name'] is not None and not alias) or e['ref']:
e_name = e['name'] or type_name # for refs, use the type name
d[e_name] = fn
else:
log.debug('complexContent/simpleType/element %s = %s' % (element_name, type_name))
d[None] = fn
if e is not None and e.get_local_name() == 'extension' and e.children():
# extend base element:
process_element(elements, element_name, e.children(), element_type, xsd_uri, dialect, namespace, qualified)
elements.setdefault(make_key(element_name, element_type, namespace), OrderedDict()).update(d)
def postprocess_element(elements, processed):
"""Fix unresolved references (elements referenced before its definition, thanks .net)"""
# avoid already processed elements:
if elements in processed:
return
processed.append(elements)
for k, v in elements.items():
if isinstance(v, OrderedDict):
if v != elements: # TODO: fix recursive elements
postprocess_element(v, processed)
if None in v and v[None]: # extension base?
if isinstance(v[None], dict):
for i, kk in enumerate(v[None]):
# extend base -keep orginal order-
if v[None] is not None:
elements[k].insert(kk, v[None][kk], i)
del v[None]
else: # "alias", just replace
log.debug('Replacing %s = %s' % (k, v[None]))
elements[k] = v[None]
#break
if v.array:
elements[k] = [v] # convert arrays to python lists
if isinstance(v, list):
for n in v: # recurse list
if isinstance(n, (OrderedDict, list)):
#if n != elements: # TODO: fix recursive elements
postprocess_element(n, processed)
def get_message(messages, message_name, part_name):
if part_name:
# get the specific part of the message:
return messages.get((message_name, part_name))
else:
# get the first part for the specified message:
for (message_name_key, part_name_key), message in messages.items():
if message_name_key == message_name:
return message
get_local_name = lambda s: s and str((':' in s) and s.split(':')[1] or s)
get_namespace_prefix = lambda s: s and str((':' in s) and s.split(':')[0] or None)
def preprocess_schema(schema, imported_schemas, elements, xsd_uri, dialect, http, cache, force_download, wsdl_basedir, global_namespaces=None, qualified=False):
"""Find schema elements and complex types"""
from .simplexml import SimpleXMLElement # here to avoid recursive imports
# analyze the namespaces used in this schema
local_namespaces = {}
for k, v in schema[:]:
if k.startswith("xmlns"):
local_namespaces[get_local_name(k)] = v
if k == 'targetNamespace':
# URI namespace reference for this schema
if v == "urn:DefaultNamespace":
v = global_namespaces[None]
local_namespaces[None] = v
if k == 'elementFormDefault':
qualified = (v == "qualified")
# add schema namespaces to the global namespace dict = {URI: ns prefix}
for ns in local_namespaces.values():
if ns not in global_namespaces:
global_namespaces[ns] = 'ns%s' % len(global_namespaces)
for element in schema.children() or []:
if element.get_local_name() in ('import', 'include',):
schema_namespace = element['namespace']
schema_location = element['schemaLocation']
if schema_location is None:
log.debug('Schema location not provided for %s!' % schema_namespace)
continue
if schema_location in imported_schemas:
log.debug('Schema %s already imported!' % schema_location)
continue
imported_schemas[schema_location] = schema_namespace
log.debug('Importing schema %s from %s' % (schema_namespace, schema_location))
# Open uri and read xml:
xml = fetch(schema_location, http, cache, force_download, wsdl_basedir)
# Parse imported XML schema (recursively):
imported_schema = SimpleXMLElement(xml, namespace=xsd_uri)
preprocess_schema(imported_schema, imported_schemas, elements, xsd_uri, dialect, http, cache, force_download, wsdl_basedir, global_namespaces, qualified)
element_type = element.get_local_name()
if element_type in ('element', 'complexType', "simpleType"):
namespace = local_namespaces[None] # get targetNamespace
element_ns = global_namespaces[ns] # get the prefix
element_name = element['name']
log.debug("Parsing Element %s: %s" % (element_type, element_name))
if element.get_local_name() == 'complexType':
children = element.children()
elif element.get_local_name() == 'simpleType':
children = element('restriction', ns=xsd_uri)
elif element.get_local_name() == 'element' and element['type']:
children = element
else:
children = element.children()
if children:
children = children.children()
elif element.get_local_name() == 'element':
children = element
if children:
process_element(elements, element_name, children, element_type, xsd_uri, dialect, namespace, qualified)
# simplexml utilities:
try:
_strptime = datetime.datetime.strptime
except AttributeError: # python2.4
_strptime = lambda s, fmt: datetime.datetime(*(time.strptime(s, fmt)[:6]))
# Functions to serialize/deserialize special immutable types:
def datetime_u(s):
fmt = "%Y-%m-%dT%H:%M:%S"
try:
return _strptime(s, fmt)
except ValueError:
try:
# strip utc offset
if s[-3] == ":" and s[-6] in (' ', '-', '+'):
warnings.warn('removing unsupported UTC offset', RuntimeWarning)
s = s[:-6]
# parse microseconds
try:
return _strptime(s, fmt + ".%f")
except:
return _strptime(s, fmt)
except ValueError:
# strip microseconds (not supported in this platform)
if "." in s:
warnings.warn('removing unsuppported microseconds', RuntimeWarning)
s = s[:s.index(".")]
return _strptime(s, fmt)
datetime_m = lambda dt: dt.isoformat()
date_u = lambda s: _strptime(s[0:10], "%Y-%m-%d").date()
date_m = lambda d: d.strftime("%Y-%m-%d")
time_u = lambda s: _strptime(s, "%H:%M:%S").time()
time_m = lambda d: d.strftime("%H%M%S")
bool_u = lambda s: {'0': False, 'false': False, '1': True, 'true': True}[s]
bool_m = lambda s: {False: 'false', True: 'true'}[s]
# aliases:
class Alias(object):
def __init__(self, py_type, xml_type):
self.py_type, self.xml_type = py_type, xml_type
def __call__(self, value):
return self.py_type(value)
def __repr__(self):
return "<alias '%s' for '%s'>" % (self.xml_type, self.py_type)
if sys.version > '3':
long = Alias(int, 'long')
byte = Alias(str, 'byte')
short = Alias(int, 'short')
double = Alias(float, 'double')
integer = Alias(long, 'integer')
DateTime = datetime.datetime
Date = datetime.date
Time = datetime.time
# Define convertion function (python type): xml schema type
TYPE_MAP = {
unicode: 'string',
bool: 'boolean',
short: 'short',
byte: 'byte',
int: 'int',
long: 'long',
integer: 'integer',
float: 'float',
double: 'double',
Decimal: 'decimal',
datetime.datetime: 'dateTime',
datetime.date: 'date',
}
TYPE_MARSHAL_FN = {
datetime.datetime: datetime_m,
datetime.date: date_m,
bool: bool_m
}
TYPE_UNMARSHAL_FN = {
datetime.datetime: datetime_u,
datetime.date: date_u,
bool: bool_u,
str: unicode,
}
REVERSE_TYPE_MAP = dict([(v, k) for k, v in TYPE_MAP.items()])
REVERSE_TYPE_MAP.update({
'base64Binary': str,
})
# insert str here to avoid collision in REVERSE_TYPE_MAP (i.e. decoding errors)
if str not in TYPE_MAP:
TYPE_MAP[str] = 'string'
class OrderedDict(dict):
"""Minimal ordered dictionary for xsd:sequences"""
def __init__(self):
self.__keys = []
self.array = False
self.namespace = None
self.qualified = None
def __setitem__(self, key, value):
if key not in self.__keys:
self.__keys.append(key)
dict.__setitem__(self, key, value)
def insert(self, key, value, index=0):
if key not in self.__keys:
self.__keys.insert(index, key)
dict.__setitem__(self, key, value)
def __delitem__(self, key):
if key in self.__keys:
self.__keys.remove(key)
dict.__delitem__(self, key)
def __iter__(self):
return iter(self.__keys)
def keys(self):
return self.__keys
def items(self):
return [(key, self[key]) for key in self.__keys]
def update(self, other):
for k, v in other.items():
self[k] = v
# do not change if we are an array but the other is not:
if isinstance(other, OrderedDict) and not self.array:
self.array = other.array
if isinstance(other, OrderedDict) and not self.namespace:
self.namespace = other.namespace
self.qualified = other.qualified
def copy(self):
"Make a duplicate"
new = OrderedDict()
new.update(self)
return new
def __str__(self):
return "%s" % dict.__str__(self)
def __repr__(self):
s = "{%s}" % ", ".join(['%s: %s' % (repr(k), repr(v)) for k, v in self.items()])
if self.array and False:
s = "[%s]" % s
return s
+236 -146
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python
# -*- coding: latin-1 -*-
#!/usr/bin/python
# -*- coding: utf-8 -*-
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation; either version 3, or (at your option) any later
@@ -10,30 +10,41 @@
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
# for more details.
"Simple SOAP Server implementation"
"""Pythonic simple SOAP Server implementation"""
__author__ = "Mariano Reingart (reingart@gmail.com)"
__copyright__ = "Copyright (C) 2010 Mariano Reingart"
__license__ = "LGPL 3.0"
__version__ = "1.03c"
from __future__ import unicode_literals
import sys
if sys.version > '3':
unicode = str
import datetime
import sys
import logging
import warnings
import re
import traceback
from simplexml import SimpleXMLElement, TYPE_MAP, Date, Decimal
try:
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
except ImportError:
from http.server import BaseHTTPRequestHandler, HTTPServer
from . import __author__, __copyright__, __license__, __version__
from .simplexml import SimpleXMLElement, TYPE_MAP, Date, Decimal
log = logging.getLogger(__name__)
# Deprecated
DEBUG = False
NS_RX=re.compile(r'xmlns:(\w+)="(.+?)"')
# Deprecated?
NS_RX = re.compile(r'xmlns:(\w+)="(.+?)"')
class SoapDispatcher(object):
"Simple Dispatcher for SOAP Server"
def __init__(self, name, documentation='', action='', location='',
namespace=None, prefix=False,
soap_uri="http://schemas.xmlsoap.org/soap/envelope/",
"""Simple Dispatcher for SOAP Server"""
def __init__(self, name, documentation='', action='', location='',
namespace=None, prefix=False,
soap_uri="http://schemas.xmlsoap.org/soap/envelope/",
soap_ns='soap',
namespaces={},
pretty=False,
@@ -45,13 +56,13 @@ class SoapDispatcher(object):
:param namespaces: Specify additional namespaces; example: {'external': 'http://external.mt.moboperator'}
:param pretty: Prettifies generated xmls
:param debug: Use to add tracebacks in generated xmls.
Multiple namespaces
===================
It is possible to support multiple namespaces.
You need to specify additional namespaces by passing `namespace` parameter.
>>> dispatcher = SoapDispatcher(
... name = "MTClientWS",
... location = "http://localhost:8008/ws/MTClientWS",
@@ -59,13 +70,13 @@ class SoapDispatcher(object):
... namespace = "http://external.mt.moboperator", prefix="external",
... documentation = 'moboperator MTClientWS',
... namespaces = {
... 'external': 'http://external.mt.moboperator',
... 'external': 'http://external.mt.moboperator',
... 'model': 'http://model.common.mt.moboperator'
... },
... ns = True)
Now the registered method must return node names with namespaces' prefixes.
>>> def _multi_ns_func(self, serviceMsisdn):
... ret = {
... 'external:activateSubscriptionsReturn': [
@@ -73,23 +84,22 @@ class SoapDispatcher(object):
... {'model:description': 'desc'},
... ]}
... return ret
Our prefixes will be changed to those used by the client.
"""
self.methods = {}
self.name = name
self.documentation = documentation
self.action = action # base SoapAction
self.action = action # base SoapAction
self.location = location
self.namespace = namespace # targetNamespace
self.namespace = namespace # targetNamespace
self.prefix = prefix
self.soap_ns = soap_ns
self.soap_uri = soap_uri
self.namespaces = namespaces
self.pretty = pretty
self.debug = debug
@staticmethod
def _extra_namespaces(xml, ns):
"""Extends xml with extra namespaces.
@@ -99,56 +109,57 @@ class SoapDispatcher(object):
if ns:
_tpl = 'xmlns:%s="%s"'
_ns_str = " ".join([_tpl % (prefix, uri) for uri, prefix in ns.items() if uri not in xml])
xml = xml.replace('/>', ' '+_ns_str+'/>')
xml = xml.replace('/>', ' ' + _ns_str + '/>')
return xml
def register_function(self, name, fn, returns=None, args=None, doc=None):
self.methods[name] = fn, returns, args, doc or getattr(fn, "__doc__", "")
def dispatch(self, xml, action=None):
"Receive and proccess SOAP call"
def dispatch(self, xml, action=None, fault=None):
"""Receive and process SOAP call, returns the xml"""
# a dict can be sent in fault to expose it to the caller
# default values:
prefix = self.prefix
ret = fault = None
ret = None
if fault is None:
fault = {}
soap_ns, soap_uri = self.soap_ns, self.soap_uri
soap_fault_code = 'VersionMismatch'
name = None
# namespaces = [('model', 'http://model.common.mt.moboperator'), ('external', 'http://external.mt.moboperator')]
_ns_reversed = dict(((v,k) for k,v in self.namespaces.iteritems())) # Switch keys-values
_ns_reversed = dict(((v, k) for k, v in self.namespaces.items())) # Switch keys-values
# _ns_reversed = {'http://external.mt.moboperator': 'external', 'http://model.common.mt.moboperator': 'model'}
try:
request = SimpleXMLElement(xml, namespace=self.namespace)
# detect soap prefix and uri (xmlns attributes of Envelope)
for k, v in request[:]:
if v in ("http://schemas.xmlsoap.org/soap/envelope/",
"http://www.w3.org/2003/05/soap-env",):
"http://www.w3.org/2003/05/soap-env",):
soap_ns = request.attributes()[k].localName
soap_uri = request.attributes()[k].value
# If the value from attributes on Envelope is in additional namespaces
elif v in self.namespaces.values():
_ns = request.attributes()[k].localName
_uri = request.attributes()[k].value
_ns_reversed[_uri] = _ns # update with received alias
_ns_reversed[_uri] = _ns # update with received alias
# Now we change 'external' and 'model' to the received forms i.e. 'ext' and 'mod'
# After that we know how the client has prefixed additional namespaces
ns = NS_RX.findall(xml)
for k, v in ns:
if v in self.namespaces.values():
_ns_reversed[v] = k
soap_fault_code = 'Client'
# parse request message and get local method
method = request('Body', ns=soap_uri).children()(0)
if action:
# method name = action
# method name = action
name = action[len(self.action)+1:-1]
prefix = self.prefix
if not action or not name:
@@ -159,21 +170,21 @@ class SoapDispatcher(object):
log.debug('dispatch method: %s', name)
function, returns_types, args_types, doc = self.methods[name]
log.debug('returns_types %s', returns_types)
# de-serialize parameters (if type definitions given)
if args_types:
args = method.children().unmarshall(args_types)
elif args_types is None:
args = {'request': method} # send raw request
args = {'request': method} # send raw request
else:
args = {} # no parameters
args = {} # no parameters
soap_fault_code = 'Server'
# execute function
ret = function(**args)
log.debug('dispathed method returns: %s', ret)
except Exception: # This shouldn't be one huge try/except
except Exception: # This shouldn't be one huge try/except
import sys
etype, evalue, etb = sys.exc_info()
log.error(traceback.format_exc())
@@ -182,43 +193,43 @@ class SoapDispatcher(object):
detail += '\n\nXML REQUEST\n\n' + xml
else:
detail = None
fault = {'faultcode': "%s.%s" % (soap_fault_code, etype.__name__),
'faultstring': unicode(evalue),
'detail': detail}
fault.update({'faultcode': "%s.%s" % (soap_fault_code, etype.__name__),
'faultstring': evalue,
'detail': detail})
# build response message
if not prefix:
xml = """<%(soap_ns)s:Envelope xmlns:%(soap_ns)s="%(soap_uri)s"/>"""
xml = """<%(soap_ns)s:Envelope xmlns:%(soap_ns)s="%(soap_uri)s"/>"""
else:
xml = """<%(soap_ns)s:Envelope xmlns:%(soap_ns)s="%(soap_uri)s"
xmlns:%(prefix)s="%(namespace)s"/>"""
xmlns:%(prefix)s="%(namespace)s"/>"""
xml %= { # a %= {} is a shortcut for a = a % {}
'namespace': self.namespace,
'namespace': self.namespace,
'prefix': prefix,
'soap_ns': soap_ns,
'soap_ns': soap_ns,
'soap_uri': soap_uri
}
# Now we add extra namespaces
xml = SoapDispatcher._extra_namespaces(xml, _ns_reversed)
# Change our namespace alias to that given by the client.
# We put [('model', 'http://model.common.mt.moboperator'), ('external', 'http://external.mt.moboperator')]
# mix it with {'http://external.mt.moboperator': 'ext', 'http://model.common.mt.moboperator': 'mod'}
mapping = dict(((k, _ns_reversed[v]) for k,v in self.namespaces.iteritems())) # Switch keys-values and change value
mapping = dict(((k, _ns_reversed[v]) for k, v in self.namespaces.items())) # Switch keys-values and change value
# and get {'model': u'mod', 'external': u'ext'}
response = SimpleXMLElement(xml,
response = SimpleXMLElement(xml,
namespace=self.namespace,
namespaces_map = mapping,
namespaces_map=mapping,
prefix=prefix)
response['xmlns:xsi'] = "http://www.w3.org/2001/XMLSchema-instance"
response['xmlns:xsd'] = "http://www.w3.org/2001/XMLSchema"
body = response.add_child("%s:Body" % soap_ns, ns=False)
if fault:
# generate a Soap Fault (with the python exception)
body.marshall("%s:Fault" % soap_ns, fault, ns=False)
@@ -226,14 +237,24 @@ class SoapDispatcher(object):
# return normal value
res = body.add_child("%sResponse" % name, ns=prefix)
if not prefix:
res['xmlns'] = self.namespace # add target namespace
res['xmlns'] = self.namespace # add target namespace
# serialize returned values (response) if type definition available
if returns_types:
if not isinstance(ret, dict):
# TODO: full sanity check of type structure (recursive)
complex_type = isinstance(ret, dict)
if complex_type:
# check if type mapping correlates with return value
types_ok = all([k in returns_types for k in ret.keys()])
if not types_ok:
warnings.warn("Return value doesn't match type structure: "
"%s vs %s" % (str(returns_types), str(ret)))
if not complex_type or not types_ok:
# backward compatibility for scalar and simple types
res.marshall(returns_types.keys()[0], ret, )
else:
for k,v in ret.items():
# new style for complex classes
for k, v in ret.items():
res.marshall(k, v)
elif returns_types is None:
# merge xmlelement returned
@@ -246,16 +267,16 @@ class SoapDispatcher(object):
# Introspection functions:
def list_methods(self):
"Return a list of aregistered operations"
return [(method, doc) for method, (function, returns, args, doc) in self.methods.items()]
"""Return a list of aregistered operations"""
return [(method, doc) for method, (function, returns, args, doc) in self.methods.items()]
def help(self, method=None):
"Generate sample request and response messages"
"""Generate sample request and response messages"""
(function, returns, args, doc) = self.methods[method]
xml = """
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body><%(method)s xmlns="%(namespace)s"/></soap:Body>
</soap:Envelope>""" % {'method':method, 'namespace':self.namespace}
</soap:Envelope>""" % {'method': method, 'namespace': self.namespace}
request = SimpleXMLElement(xml, namespace=self.namespace, prefix=self.prefix)
if args:
items = args.items()
@@ -263,13 +284,13 @@ class SoapDispatcher(object):
items = [('value', None)]
else:
items = []
for k,v in items:
for k, v in items:
request(method).marshall(k, v, add_comments=True, ns=False)
xml = """
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body><%(method)sResponse xmlns="%(namespace)s"/></soap:Body>
</soap:Envelope>""" % {'method':method, 'namespace':self.namespace}
</soap:Envelope>""" % {'method': method, 'namespace': self.namespace}
response = SimpleXMLElement(xml, namespace=self.namespace, prefix=self.prefix)
if returns:
items = returns.items()
@@ -277,16 +298,15 @@ class SoapDispatcher(object):
items = [('value', None)]
else:
items = []
for k,v in items:
response('%sResponse'%method).marshall(k, v, add_comments=True, ns=False)
for k, v in items:
response('%sResponse' % method).marshall(k, v, add_comments=True, ns=False)
return request.as_xml(pretty=True), response.as_xml(pretty=True), doc
def wsdl(self):
"Generate Web Service Description v1.1"
"""Generate Web Service Description v1.1"""
xml = """<?xml version="1.0"?>
<wsdl:definitions name="%(name)s"
<wsdl:definitions name="%(name)s"
targetNamespace="%(namespace)s"
xmlns:tns="%(namespace)s"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
@@ -307,7 +327,7 @@ class SoapDispatcher(object):
for method, (function, returns, args, doc) in self.methods.items():
# create elements:
def parse_element(name, values, array=False, complex=False):
if not complex:
element = wsdl('wsdl:types')('xsd:schema').add_child('xsd:element')
@@ -326,38 +346,40 @@ class SoapDispatcher(object):
all = complex.add_child("xsd:all")
elif items:
all = complex.add_child("xsd:sequence")
for k,v in items:
for k, v in items:
e = all.add_child("xsd:element")
e['name'] = k
if array:
e[:]={'minOccurs': "0", 'maxOccurs': "unbounded"}
e[:] = {'minOccurs': "0", 'maxOccurs': "unbounded"}
if v in TYPE_MAP.keys():
t='xsd:%s' % TYPE_MAP[v]
t = 'xsd:%s' % TYPE_MAP[v]
elif v is None:
t='xsd:anyType'
t = 'xsd:anyType'
elif isinstance(v, list):
n="ArrayOf%s%s" % (name, k)
n = "ArrayOf%s%s" % (name, k)
l = []
for d in v:
l.extend(d.items())
parse_element(n, l, array=True, complex=True)
t = "tns:%s" % n
elif isinstance(v, dict):
n="%s%s" % (name, k)
elif isinstance(v, dict):
n = "%s%s" % (name, k)
parse_element(n, v.items(), complex=True)
t = "tns:%s" % n
else:
raise TypeError("unknonw type v for marshalling" % str(v))
e.add_attribute('type', t)
parse_element("%s" % method, args and args.items())
parse_element("%sResponse" % method, returns and returns.items())
# create messages:
for m,e in ('Input',''), ('Output','Response'):
for m, e in ('Input', ''), ('Output', 'Response'):
message = wsdl.add_child('wsdl:message')
message['name'] = "%s%s" % (method, m)
part = message.add_child("wsdl:part")
part[:] = {'name': 'parameters',
'element': 'tns:%s%s' % (method,e)}
part[:] = {'name': 'parameters',
'element': 'tns:%s%s' % (method, e)}
# create ports
portType = wsdl.add_child('wsdl:portType')
@@ -397,18 +419,18 @@ class SoapDispatcher(object):
service = wsdl.add_child('wsdl:service')
service["name"] = "%sService" % self.name
service.add_child('wsdl:documentation', text=self.documentation)
port=service.add_child('wsdl:port')
port = service.add_child('wsdl:port')
port["name"] = "%s" % self.name
port["binding"] = "tns:%sBinding" % self.name
soapaddress = port.add_child('soap:address')
soapaddress["location"] = self.location
return wsdl.as_xml(pretty=True)
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
class SOAPHandler(BaseHTTPRequestHandler):
def do_GET(self):
"User viewable help information and wsdl"
"""User viewable help information and wsdl"""
args = self.path[1:].split("?")
if self.path != "/" and args[0] not in self.server.dispatcher.methods.keys():
self.send_error(404, "Method not found: %s" % args[0])
@@ -419,92 +441,160 @@ class SOAPHandler(BaseHTTPRequestHandler):
else:
# return supplied method help (?request or ?response messages)
req, res, doc = self.server.dispatcher.help(args[0])
if len(args)==1 or args[1]=="request":
if len(args) == 1 or args[1] == "request":
response = req
else:
response = res
response = res
self.send_response(200)
self.send_header("Content-type", "text/xml")
self.end_headers()
self.wfile.write(response)
def do_POST(self):
"SOAP POST gateway"
self.send_response(200)
"""SOAP POST gateway"""
request = self.rfile.read(int(self.headers.getheader('content-length')))
fault = {}
# execute the method
response = self.server.dispatcher.dispatch(request, fault=fault)
# check if fault dict was completed (faultcode, faultstring, detail)
if fault:
self.send_response(500)
else:
self.send_response(200)
self.send_header("Content-type", "text/xml")
self.end_headers()
request = self.rfile.read(int(self.headers.getheader('content-length')))
response = self.server.dispatcher.dispatch(request)
self.wfile.write(response)
if __name__=="__main__":
import sys
class WSGISOAPHandler(object):
def __init__(self, dispatcher):
self.dispatcher = dispatcher
def __call__(self, environ, start_response):
return self.handler(environ, start_response)
def handler(self, environ, start_response):
if environ['REQUEST_METHOD'] == 'GET':
return self.do_get(environ, start_response)
elif environ['REQUEST_METHOD'] == 'POST':
return self.do_post(environ, start_response)
else:
start_response('405 Method not allowed', [('Content-Type', 'text/plain')])
return ['Method not allowed']
def do_get(self, environ, start_response):
path = environ.get('PATH_INFO').lstrip('/')
query = environ.get('QUERY_STRING')
if path != "" and path not in self.dispatcher.methods.keys():
start_response('404 Not Found', [('Content-Type', 'text/plain')])
return ["Method not found: %s" % path]
elif path == "":
# return wsdl if no method supplied
response = self.dispatcher.wsdl()
else:
# return supplied method help (?request or ?response messages)
req, res, doc = self.dispatcher.help(path)
if len(query) == 0 or query == "request":
response = req
else:
response = res
start_response('200 OK', [('Content-Type', 'text/xml'), ('Content-Length', str(len(response)))])
return [response]
def do_post(self, environ, start_response):
length = int(environ['CONTENT_LENGTH'])
request = environ['wsgi.input'].read(length)
response = self.dispatcher.dispatch(request)
start_response('200 OK', [('Content-Type', 'text/xml'), ('Content-Length', str(len(response)))])
return [response]
if __name__ == "__main__":
dispatcher = SoapDispatcher(
name = "PySimpleSoapSample",
location = "http://localhost:8008/",
action = 'http://localhost:8008/', # SOAPAction
namespace = "http://example.com/pysimplesoapsamle/", prefix="ns0",
documentation = 'Example soap service using PySimpleSoap',
trace = True,
ns = True)
def adder(p,c, dt=None):
"Add several values"
print c[0]['d'],c[1]['d'],
import datetime
name="PySimpleSoapSample",
location="http://localhost:8008/",
action='http://localhost:8008/', # SOAPAction
namespace="http://example.com/pysimplesoapsamle/", prefix="ns0",
documentation='Example soap service using PySimpleSoap',
trace=True, debug=True,
ns=True)
def adder(p, c, dt=None):
"""Add several values"""
dt = dt + datetime.timedelta(365)
return {'ab': p['a']+p['b'], 'dd': c[0]['d']+c[1]['d'], 'dt': dt}
return {'ab': p['a'] + p['b'], 'dd': c[0]['d'] + c[1]['d'], 'dt': dt}
def dummy(in0):
"Just return input"
"""Just return input"""
return in0
def echo(request):
"Copy request->response (generic, any type)"
"""Copy request->response (generic, any type)"""
return request.value
dispatcher.register_function('Adder', adder,
returns={'AddResult': {'ab': int, 'dd': str } },
args={'p': {'a': int,'b': int}, 'dt': Date, 'c': [{'d': Decimal}]})
dispatcher.register_function(
'Adder', adder,
returns={'AddResult': {'ab': int, 'dd': unicode, 'dt': datetime.date}},
args={'p': {'a': int, 'b': int}, 'dt': Date, 'c': [{'d': Decimal}]}
)
dispatcher.register_function('Dummy', dummy,
returns={'out0': str},
args={'in0': str})
dispatcher.register_function(
'Dummy', dummy,
returns={'out0': str},
args={'in0': str}
)
dispatcher.register_function('Echo', echo)
if '--local' in sys.argv:
wsdl=dispatcher.wsdl()
print wsdl
# Commented because path is platform dependent
# Looks that it doesnt matter.
# open("C:/test.wsdl","w").write(wsdl)
wsdl = dispatcher.wsdl()
for method, doc in dispatcher.list_methods():
request, response, doc = dispatcher.help(method)
##print request
##print response
if '--serve' in sys.argv:
print "Starting server..."
log.info("Starting server...")
httpd = HTTPServer(("", 8008), SOAPHandler)
httpd.dispatcher = dispatcher
httpd.serve_forever()
if '--wsgi-serve' in sys.argv:
log.info("Starting wsgi server...")
from wsgiref.simple_server import make_server
application = WSGISOAPHandler(dispatcher)
wsgid = make_server('', 8008, application)
wsgid.serve_forever()
if '--consume' in sys.argv:
from client import SoapClient
from .client import SoapClient
client = SoapClient(
location = "http://localhost:8008/",
action = 'http://localhost:8008/', # SOAPAction
namespace = "http://example.com/sample.wsdl",
location="http://localhost:8008/",
action='http://localhost:8008/', # SOAPAction
namespace="http://example.com/sample.wsdl",
soap_ns='soap',
trace = True,
ns = False)
response = client.Adder(p={'a':1,'b':2},dt='20100724',c=[{'d':'1.20'},{'d':'2.01'}])
trace=True,
ns=False
)
p = {'a': 1, 'b': 2}
c = [{'d': '1.20'}, {'d': '2.01'}]
response = client.Adder(p=p, dt='2010-07-24', c=c)
result = response.AddResult
print int(result.ab)
print str(result.dd)
log.info(int(result.ab))
log.info(str(result.dd))
if '--consume-wsdl' in sys.argv:
from .client import SoapClient
client = SoapClient(
wsdl="http://localhost:8008/",
)
p = {'a': 1, 'b': 2}
c = [{'d': '1.20'}, {'d': '2.01'}]
dt = datetime.date.today()
response = client.Adder(p=p, dt=dt, c=c)
result = response['AddResult']
log.info(int(result['ab']))
log.info(str(result['dd']))
+185 -262
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python
# -*- coding: latin-1 -*-
#!/usr/bin/python
# -*- coding: utf-8 -*-
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation; either version 3, or (at your option) any later
@@ -10,164 +10,47 @@
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
# for more details.
"Simple XML manipulation"
"""Simple XML manipulation"""
__author__ = "Mariano Reingart (reingart@gmail.com)"
__copyright__ = "Copyright (C) 2008/009 Mariano Reingart"
__license__ = "LGPL 3.0"
__version__ = "1.03a"
import datetime
from __future__ import unicode_literals
import sys
if sys.version > '3':
basestring = str
unicode = str
import logging
import re
import time
import warnings
import xml.dom.minidom
from decimal import Decimal
from . import __author__, __copyright__, __license__, __version__
# Utility functions used for marshalling, moved aside for readability
from .helpers import TYPE_MAP, TYPE_MARSHAL_FN, TYPE_UNMARSHAL_FN, \
REVERSE_TYPE_MAP, OrderedDict, Date, Decimal
log = logging.getLogger(__name__)
logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.WARNING)
DEBUG = False
try:
_strptime = datetime.datetime.strptime
except AttributeError: # python2.4
_strptime = lambda s, fmt: datetime.datetime(*(time.strptime(s, fmt)[:6]))
# Functions to serialize/deserialize special immutable types:
def datetime_u(s):
fmt = "%Y-%m-%dT%H:%M:%S"
try:
return _strptime(s, fmt)
except ValueError:
try:
# strip utc offset
if s[-3] == ":" and s[-6] in (' ', '-', '+'):
warnings.warn('removing unsupported UTC offset', RuntimeWarning)
s = s[:-6]
# parse microseconds
try:
return _strptime(s, fmt + ".%f")
except:
return _strptime(s, fmt)
except ValueError:
# strip microseconds (not supported in this platform)
if "." in s:
warnings.warn('removing unsuppported microseconds', RuntimeWarning)
s = s[:s.index(".")]
return _strptime(s, fmt)
datetime_m = lambda dt: dt.isoformat('T')
date_u = lambda s: _strptime(s[0:10], "%Y-%m-%d").date()
date_m = lambda d: d.strftime("%Y-%m-%d")
time_u = lambda s: _strptime(s, "%H:%M:%S").time()
time_m = lambda d: d.strftime("%H%M%S")
bool_u = lambda s: {'0':False, 'false': False, '1': True, 'true': True}[s]
bool_m = lambda s: {False: 'false', True: 'true'}[s]
# aliases:
class Alias(object):
def __init__(self, py_type, xml_type):
self.py_type, self.xml_type = py_type, xml_type
def __call__(self, value):
return self.py_type(value)
def __repr__(self):
return "<alias '%s' for '%s'>" % (self.xml_type, self.py_type)
byte = Alias(str,'byte')
short = Alias(int,'short')
double = Alias(float,'double')
integer = Alias(long,'integer')
DateTime = datetime.datetime
Date = datetime.date
Time = datetime.time
# Define convertion function (python type): xml schema type
TYPE_MAP = {
str:'string',
unicode:'string',
bool:'boolean',
short:'short',
byte:'byte',
int:'int',
long:'long',
integer:'integer',
float:'float',
double:'double',
Decimal:'decimal',
datetime.datetime:'dateTime',
datetime.date:'date',
}
TYPE_MARSHAL_FN = {
datetime.datetime:datetime_m,
datetime.date:date_m,
bool:bool_m
}
TYPE_UNMARSHAL_FN = {
datetime.datetime:datetime_u,
datetime.date:date_u,
bool:bool_u,
str:unicode,
}
REVERSE_TYPE_MAP = dict([(v,k) for k,v in TYPE_MAP.items()])
class OrderedDict(dict):
"Minimal ordered dictionary for xsd:sequences"
def __init__(self):
self.__keys = []
self.array = False
def __setitem__(self, key, value):
if key not in self.__keys:
self.__keys.append(key)
dict.__setitem__(self, key, value)
def insert(self, key, value, index=0):
if key not in self.__keys:
self.__keys.insert(index, key)
dict.__setitem__(self, key, value)
def __delitem__(self, key):
if key in self.__keys:
self.__keys.remove(key)
dict.__delitem__(self, key)
def __iter__(self):
return iter(self.__keys)
def keys(self):
return self.__keys
def items(self):
return [(key, self[key]) for key in self.__keys]
def update(self, other):
for k,v in other.items():
self[k] = v
if isinstance(other, OrderedDict):
self.array = other.array
def __str__(self):
return "*%s*" % dict.__str__(self)
def __repr__(self):
s= "*{%s}*" % ", ".join(['%s: %s' % (repr(k),repr(v)) for k,v in self.items()])
if self.array and False:
s = "[%s]" % s
return s
class SimpleXMLElement(object):
"Simple XML manipulation (simil PHP)"
def __init__(self, text = None, elements = None, document = None,
namespace = None, prefix=None, namespaces_map={}):
"""Simple XML manipulation (simil PHP)"""
def __init__(self, text=None, elements=None, document=None,
namespace=None, prefix=None, namespaces_map={}, jetty=False):
"""
:param namespaces_map: How to map our namespace prefix to that given by the client;
{prefix: received_prefix}
"""
self.__namespaces_map = namespaces_map
_rx = "|".join(namespaces_map.keys()) # {'external': 'ext', 'model': 'mod'} -> 'external|model'
self.__ns_rx = re.compile(r"^(%s):.*$" % _rx) # And now we build an expression ^(external|model):.*$
# to find prefixes in all xml nodes i.e.: <model:code>1</model:code>
# and later change that to <mod:code>1</mod:code>
_rx = "|".join(namespaces_map.keys()) # {'external': 'ext', 'model': 'mod'} -> 'external|model'
self.__ns_rx = re.compile(r"^(%s):.*$" % _rx) # And now we build an expression ^(external|model):.*$
# to find prefixes in all xml nodes i.e.: <model:code>1</model:code>
# and later change that to <mod:code>1</mod:code>
self.__ns = namespace
self.__prefix = prefix
self.__jetty = jetty # special list support
if text is not None:
try:
self.__document = xml.dom.minidom.parseString(text)
@@ -178,78 +61,81 @@ class SimpleXMLElement(object):
else:
self.__elements = elements
self.__document = document
def add_child(self, name, text=None, ns=True):
"Adding a child tag to a node"
if not ns or not self.__ns:
log.debug('adding %s', name)
"""Adding a child tag to a node"""
if not ns or self.__ns is False:
##log.debug('adding %s without namespace', name)
element = self.__document.createElement(name)
else:
log.debug('adding %s ns "%s" %s', name, self.__ns, ns)
if self.__prefix:
##log.debug('adding %s ns "%s" %s', name, self.__ns, ns)
if isinstance(ns, basestring):
element = self.__document.createElement(name)
if ns:
element.setAttribute("xmlns", ns)
elif self.__prefix:
element = self.__document.createElementNS(self.__ns, "%s:%s" % (self.__prefix, name))
else:
element = self.__document.createElementNS(self.__ns, name)
# don't append null tags!
if text is not None:
if isinstance(text, unicode):
element.appendChild(self.__document.createTextNode(text))
else:
element.appendChild(self.__document.createTextNode(str(text)))
element.appendChild(self.__document.createTextNode(text))
self._element.appendChild(element)
return SimpleXMLElement(
elements=[element],
document=self.__document,
namespace=self.__ns,
prefix=self.__prefix,
namespaces_map=self.__namespaces_map)
elements=[element],
document=self.__document,
namespace=self.__ns,
prefix=self.__prefix,
jetty=self.__jetty,
namespaces_map=self.__namespaces_map
)
def __setattr__(self, tag, text):
"Add text child tag node (short form)"
"""Add text child tag node (short form)"""
if tag.startswith("_"):
object.__setattr__(self, tag, text)
else:
log.debug('__setattr__(%s, %s)', tag, text)
##log.debug('__setattr__(%s, %s)', tag, text)
self.add_child(tag, text)
def __delattr__(self, tag):
"Remove a child tag (non recursive!)"
elements=[__element for __element in self._element.childNodes
if __element.nodeType == __element.ELEMENT_NODE
]
"""Remove a child tag (non recursive!)"""
elements = [__element for __element in self._element.childNodes
if __element.nodeType == __element.ELEMENT_NODE]
for element in elements:
self._element.removeChild(element)
def add_comment(self, data):
"Add an xml comment to this child"
"""Add an xml comment to this child"""
comment = self.__document.createComment(data)
self._element.appendChild(comment)
def as_xml(self, filename=None, pretty=False):
"Return the XML representation of the document"
"""Return the XML representation of the document"""
if not pretty:
return self.__document.toxml('UTF-8')
else:
return self.__document.toprettyxml(encoding='UTF-8')
def __repr__(self):
"Return the XML representation of this tag"
"""Return the XML representation of this tag"""
# NOTE: do not use self.as_xml('UTF-8') as it returns the whole xml doc
return self._element.toxml('UTF-8')
def get_name(self):
"Return the tag name of this node"
"""Return the tag name of this node"""
return self._element.tagName
def get_local_name(self):
"Return the tag loca name (prefix:name) of this node"
"""Return the tag local name (prefix:name) of this node"""
return self._element.localName
def get_prefix(self):
"Return the namespace prefix of this node"
"""Return the namespace prefix of this node"""
return self._element.prefix
def get_namespace_uri(self, ns):
"Return the namespace uri for a prefix"
"""Return the namespace uri for a prefix"""
element = self._element
while element is not None and element.attributes is not None:
try:
@@ -257,38 +143,39 @@ class SimpleXMLElement(object):
except KeyError:
element = element.parentNode
def attributes(self):
"Return a dict of attributes for this tag"
"""Return a dict of attributes for this tag"""
#TODO: use slice syntax [:]?
return self._element.attributes
def __getitem__(self, item):
"Return xml tag attribute value or a slice of attributes (iter)"
log.debug('__getitem__(%s)', item)
"""Return xml tag attribute value or a slice of attributes (iter)"""
##log.debug('__getitem__(%s)', item)
if isinstance(item, basestring):
if self._element.hasAttribute(item):
return self._element.attributes[item].value
elif isinstance(item, slice):
# return a list with name:values
return self._element.attributes.items()[item]
return list(self._element.attributes.items())[item]
else:
# return element by index (position)
element = self.__elements[item]
return SimpleXMLElement(
elements=[element],
document=self.__document,
namespace=self.__ns,
prefix=self.__prefix,
namespaces_map=self.__namespaces_map)
elements=[element],
document=self.__document,
namespace=self.__ns,
prefix=self.__prefix,
jetty=self.__jetty,
namespaces_map=self.__namespaces_map
)
def add_attribute(self, name, value):
"Set an attribute value from a string"
"""Set an attribute value from a string"""
self._element.setAttribute(name, value)
def __setitem__(self, item, value):
"Set an attribute value"
if isinstance(item,basestring):
"""Set an attribute value"""
if isinstance(item, basestring):
self.add_attribute(item, value)
elif isinstance(item, slice):
# set multiple attributes at once
@@ -297,7 +184,7 @@ class SimpleXMLElement(object):
def __call__(self, tag=None, ns=None, children=False, root=False,
error=True, ):
"Search (even in child nodes) and return a child tag by name"
"""Search (even in child nodes) and return a child tag by name"""
try:
if root:
# return entire document
@@ -306,6 +193,7 @@ class SimpleXMLElement(object):
document=self.__document,
namespace=self.__ns,
prefix=self.__prefix,
jetty=self.__jetty,
namespaces_map=self.__namespaces_map
)
if tag is None:
@@ -317,23 +205,23 @@ class SimpleXMLElement(object):
elements = None
if isinstance(tag, int):
# return tag by index
elements=[self.__elements[tag]]
elements = [self.__elements[tag]]
if ns and not elements:
for ns_uri in isinstance(ns, (tuple, list)) and ns or (ns, ):
log.debug('searching %s by ns=%s', tag, ns_uri)
##log.debug('searching %s by ns=%s', tag, ns_uri)
elements = self._element.getElementsByTagNameNS(ns_uri, tag)
if elements:
if elements:
break
if self.__ns and not elements:
log.debug('searching %s by ns=%s', tag, self.__ns)
##log.debug('searching %s by ns=%s', tag, self.__ns)
elements = self._element.getElementsByTagNameNS(self.__ns, tag)
if not elements:
log.debug('searching %s', tag)
##log.debug('searching %s', tag)
elements = self._element.getElementsByTagName(tag)
if not elements:
#log.debug(self._element.toxml())
##log.debug(self._element.toxml())
if error:
raise AttributeError(u"No elements found")
raise AttributeError("No elements found")
else:
return
return SimpleXMLElement(
@@ -341,16 +229,17 @@ class SimpleXMLElement(object):
document=self.__document,
namespace=self.__ns,
prefix=self.__prefix,
jetty=self.__jetty,
namespaces_map=self.__namespaces_map)
except AttributeError, e:
raise AttributeError(u"Tag not found: %s (%s)" % (tag, unicode(e)))
except AttributeError as e:
raise AttributeError("Tag not found: %s (%s)" % (tag, e))
def __getattr__(self, tag):
"Shortcut for __call__"
"""Shortcut for __call__"""
return self.__call__(tag)
def __iter__(self):
"Iterate over xml tags at this level"
"""Iterate over xml tags at this level"""
try:
for __element in self.__elements:
yield SimpleXMLElement(
@@ -358,67 +247,72 @@ class SimpleXMLElement(object):
document=self.__document,
namespace=self.__ns,
prefix=self.__prefix,
jetty=self.__jetty,
namespaces_map=self.__namespaces_map)
except:
raise
def __dir__(self):
"List xml children tags names"
return [node.tagName for node
"""List xml children tags names"""
return [node.tagName for node
in self._element.childNodes
if node.nodeType != node.TEXT_NODE]
def children(self):
"Return xml children tags element"
elements=[__element for __element in self._element.childNodes
if __element.nodeType == __element.ELEMENT_NODE]
"""Return xml children tags element"""
elements = [__element for __element in self._element.childNodes
if __element.nodeType == __element.ELEMENT_NODE]
if not elements:
return None
#raise IndexError("Tag %s has no children" % self._element.tagName)
return SimpleXMLElement(
elements=elements,
document=self.__document,
namespace=self.__ns,
prefix=self.__prefix,
namespaces_map=self.__namespaces_map)
elements=elements,
document=self.__document,
namespace=self.__ns,
prefix=self.__prefix,
jetty=self.__jetty,
namespaces_map=self.__namespaces_map
)
def __len__(self):
"Return elements count"
"""Return element count"""
return len(self.__elements)
def __contains__( self, item):
"Search for a tag name in this element or child nodes"
def __contains__(self, item):
"""Search for a tag name in this element or child nodes"""
return self._element.getElementsByTagName(item)
def __unicode__(self):
"Returns the unicode text nodes of the current element"
"""Returns the unicode text nodes of the current element"""
if self._element.childNodes:
rc = u""
rc = ""
for node in self._element.childNodes:
if node.nodeType == node.TEXT_NODE:
rc = rc + node.data
return rc
return ''
def __str__(self):
"Returns the str text nodes of the current element"
return unicode(self).encode("utf8","ignore")
"""Returns the str text nodes of the current element"""
return self.__unicode__()
def __int__(self):
"Returns the integer value of the current element"
"""Returns the integer value of the current element"""
return int(self.__str__())
def __float__(self):
"Returns the float value of the current element"
"""Returns the float value of the current element"""
try:
return float(self.__str__())
except:
raise IndexError(self._element.toxml())
raise IndexError(self._element.toxml())
_element = property(lambda self: self.__elements[0])
def unmarshall(self, types, strict=True):
"Convert to python values the current serialized xml element"
#import pdb; pdb.set_trace()
"""Convert to python values the current serialized xml element"""
# types is a dict of {tag name: convertion function}
# strict=False to use default type conversion if not specified
# example: types={'p': {'a': int,'b': int}, 'c': [{'d':str}]}
@@ -435,37 +329,62 @@ class SimpleXMLElement(object):
if ref_node['id'] == href:
node = ref_node
ref_name_type = ref_node['xsi:type'].split(":")[1]
break
break
try:
fn = types[name]
except (KeyError, ), e:
if node.get_namespace_uri("soapenc"):
fn = None # ignore multirefs!
elif 'xsi:type' in node.attributes().keys():
if isinstance(types, dict):
fn = types[name]
# custom array only in the response (not defined in the WSDL):
# <results soapenc:arrayType="xsd:string[199]>
if any([k for k,v in node[:] if 'arrayType' in k]) and not isinstance(fn, list):
fn = [fn]
else:
fn = types
except (KeyError, ) as e:
if 'xsi:type' in node.attributes().keys():
xsd_type = node['xsi:type'].split(":")[1]
fn = REVERSE_TYPE_MAP[xsd_type]
try:
fn = REVERSE_TYPE_MAP[xsd_type]
except:
fn = None # ignore multirefs!
elif strict:
raise TypeError(u"Tag: %s invalid (type not found)" % (name,))
raise TypeError("Tag: %s invalid (type not found)" % (name,))
else:
# if not strict, use default type conversion
fn = unicode
fn = str
if isinstance(fn, list):
# append to existing list (if any) - unnested dict arrays -
value = d.setdefault(name, [])
children = node.children()
for child in (children and children() or []): # Readability counts
value.append(child.unmarshall(fn[0], strict))
# TODO: check if this was really needed (get first child only)
##if len(fn[0]) == 1 and children:
## children = children()
if fn and not isinstance(fn[0], dict):
# simple arrays []
for child in (children or []):
tmp_dict = child.unmarshall(fn[0], strict)
value.extend(tmp_dict.values())
elif (self.__jetty and len(fn[0]) > 1):
# Jetty array style support [{k, v}]
for parent in node:
tmp_dict = {} # unmarshall each value & mix
for child in (node.children() or []):
tmp_dict.update(child.unmarshall(fn[0], strict))
value.append(tmp_dict)
else: # .Net / Java
for child in (children or []):
value.append(child.unmarshall(fn[0], strict))
elif isinstance(fn, tuple):
value = []
_d = {}
children = node.children()
as_dict = len(fn) == 1 and isinstance(fn[0], dict)
for child in (children and children() or []): # Readability counts
for child in (children and children() or []): # Readability counts
if as_dict:
_d.update(child.unmarshall(fn[0], strict)) # Merging pairs
_d.update(child.unmarshall(fn[0], strict)) # Merging pairs
else:
value.append(child.unmarshall(fn[0], strict))
if as_dict:
@@ -477,32 +396,32 @@ class SimpleXMLElement(object):
value = tuple(_tmp)
else:
value = tuple(value)
elif isinstance(fn, dict):
##if ref_name_type is not None:
## fn = fn[ref_name_type]
children = node.children()
value = children and children.unmarshall(fn, strict)
else:
if fn is None: # xsd:anyType not unmarshalled
if fn is None: # xsd:anyType not unmarshalled
value = node
elif str(node) or fn == str:
elif unicode(node) or (fn == str and unicode(node) != ''):
try:
# get special deserialization function (if any)
fn = TYPE_UNMARSHAL_FN.get(fn,fn)
fn = TYPE_UNMARSHAL_FN.get(fn, fn)
if fn == str:
# always return an unicode object:
# (avoid encoding errors in py<3!)
value = unicode(node)
else:
value = fn(unicode(node))
except (ValueError, TypeError), e:
raise ValueError(u"Tag: %s: %s" % (name, unicode(e)))
except (ValueError, TypeError) as e:
raise ValueError("Tag: %s: %s" % (name, e))
else:
value = None
d[name] = value
return d
def _update_ns(self, name):
"""Replace the defined namespace alias with tohse used by the client."""
pref = self.__ns_rx.search(name)
@@ -513,46 +432,50 @@ class SimpleXMLElement(object):
except KeyError:
log.warning('Unknown namespace alias %s' % name)
return name
def marshall(self, name, value, add_child=True, add_comments=False,
def marshall(self, name, value, add_child=True, add_comments=False,
ns=False, add_children_ns=True):
"Analize python value and add the serialized XML element using tag name"
"""Analyze python value and add the serialized XML element using tag name"""
# Change node name to that used by a client
name = self._update_ns(name)
if isinstance(value, dict): # serialize dict (<key>value</key>)
# for the first parent node, use the document target namespace
# (ns==True) or use the namespace string uri if passed (elements)
child = add_child and self.add_child(name, ns=ns) or self
for k,v in value.items():
for k, v in value.items():
if not add_children_ns:
ns = False
else:
# for children, use the wsdl element target namespace:
ns = getattr(value, 'namespace', None)
child.marshall(k, v, add_comments=add_comments, ns=ns)
elif isinstance(value, tuple): # serialize tuple (<key>value</key>)
child = add_child and self.add_child(name, ns=ns) or self
if not add_children_ns:
ns = False
for k,v in value:
for k, v in value:
getattr(self, name).marshall(k, v, add_comments=add_comments, ns=ns)
elif isinstance(value, list): # serialize lists
child=self.add_child(name, ns=ns)
elif isinstance(value, list): # serialize lists
child = self.add_child(name, ns=ns)
if not add_children_ns:
ns = False
if add_comments:
child.add_comment("Repetitive array of:")
for t in value:
child.marshall(name, t, False, add_comments=add_comments, ns=ns)
elif isinstance(value, basestring): # do not convert strings or unicodes
self.add_child(name, value,ns=ns)
elif value is None: # sent a empty tag?
elif isinstance(value, basestring): # do not convert strings or unicodes
self.add_child(name, value, ns=ns)
elif value is None: # sent a empty tag?
self.add_child(name, ns=ns)
elif value in TYPE_MAP.keys():
# add commented placeholders for simple tipes (for examples/help only)
child = self.add_child(name, ns=ns)
child = self.add_child(name, ns=ns)
child.add_comment(TYPE_MAP[value])
else: # the rest of object types are converted to string
else: # the rest of object types are converted to string
# get special serialization function (if any)
fn = TYPE_MARSHAL_FN.get(type(value), str)
self.add_child(name, fn(value), ns=ns)
self.add_child(name, fn(value), ns=ns)
def import_node(self, other):
x = self.__document.importNode(other._element, True) # deep copy
+82 -50
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python
# -*- coding: latin-1 -*-
#!/usr/bin/python
# -*- coding: utf-8 -*-
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation; either version 3, or (at your option) any later
@@ -10,30 +10,46 @@
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
# for more details.
"Pythonic simple SOAP Client implementation"
"""Pythonic simple SOAP Client transport"""
__author__ = "Mariano Reingart (reingart@gmail.com)"
__copyright__ = "Copyright (C) 2008 Mariano Reingart"
__license__ = "LGPL 3.0"
TIMEOUT = 60
import os
import cPickle as pickle
import urllib2
from urlparse import urlparse
import tempfile
from simplexml import SimpleXMLElement, TYPE_MAP, OrderedDict
import logging
import sys
try:
import urllib2
from cookielib import CookieJar
except ImportError:
from urllib import request as urllib2
from http.cookiejar import CookieJar
from . import __author__, __copyright__, __license__, __version__, TIMEOUT
from .simplexml import SimpleXMLElement, TYPE_MAP, OrderedDict
log = logging.getLogger(__name__)
logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.WARNING)
#
# Socket wrapper to enable socket.TCP_NODELAY - this greatly speeds up transactions in Linux
# WARNING: this will modify the standard library socket module, use with care!
# TODO: implement this as a transport faciliy
# (to pass options directly to httplib2 or pycurl)
# be aware of metaclasses and socks.py (SocksiPy) used by httplib2
if False:
import socket
realsocket = socket.socket
def socketwrap(family=socket.AF_INET, type=socket.SOCK_STREAM, proto=0):
sockobj = realsocket(family, type, proto)
if type == socket.SOCK_STREAM:
sockobj.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
return sockobj
socket.socket = socketwrap
#
# We store metadata about what available transport mechanisms we have available.
#
_http_connectors = {} # libname: classimpl mapping
_http_facilities = {} # functionalitylabel: [sequence of libname] mapping
_http_connectors = {} # libname: classimpl mapping
_http_facilities = {} # functionalitylabel: [sequence of libname] mapping
class TransportBase:
@classmethod
@@ -45,27 +61,41 @@ class TransportBase:
#
try:
import httplib2
if sys.version > '3' and httplib2.__version__ <= "0.7.7":
import http.client
# httplib2 workaround: check_hostname needs a SSL context with either
# CERT_OPTIONAL or CERT_REQUIRED
# see https://code.google.com/p/httplib2/issues/detail?id=173
orig__init__ = http.client.HTTPSConnection.__init__
def fixer(self, host, port, key_file, cert_file, timeout, context,
check_hostname, *args, **kwargs):
chk = kwargs.get('disable_ssl_certificate_validation', True) ^ True
orig__init__(self, host, port=port, key_file=key_file,
cert_file=cert_file, timeout=timeout, context=context,
check_hostname=chk)
http.client.HTTPSConnection.__init__ = fixer
except ImportError:
TIMEOUT = None # timeout not supported by urllib2
TIMEOUT = None # timeout not supported by urllib2
pass
else:
class Httplib2Transport(httplib2.Http, TransportBase):
_wrapper_version = "httplib2 %s" % httplib2.__version__
_wrapper_name = 'httplib2'
def __init__(self, timeout, proxy=None, cacert=None, sessions=False):
##httplib2.debuglevel=4
kwargs = {}
if proxy:
import socks
kwargs['proxy_info'] = httplib2.ProxyInfo(proxy_type=socks.PROXY_TYPE_HTTP, **proxy)
print "using proxy", proxy
log.info("using proxy %s" % proxy)
# set optional parameters according supported httplib2 version
if httplib2.__version__ >= '0.3.0':
kwargs['timeout'] = timeout
if httplib2.__version__ >= '0.7.0':
kwargs['disable_ssl_certificate_validation'] = cacert is None
kwargs['ca_certs'] = cacert
kwargs['ca_certs'] = cacert
httplib2.Http.__init__(self, **kwargs)
_http_connectors['httplib2'] = Httplib2Transport
@@ -76,15 +106,15 @@ else:
if 'timeout' in inspect.getargspec(httplib2.Http.__init__)[0]:
_http_facilities.setdefault('timeout', []).append('httplib2')
#
# urllib2 support.
#
import urllib2
class urllib2Transport(TransportBase):
_wrapper_version = "urllib2 %s" % urllib2.__version__
_wrapper_name = 'urllib2'
_wrapper_name = 'urllib2'
def __init__(self, timeout=None, proxy=None, cacert=None, sessions=False):
import sys
if (timeout is not None) and not self.supports_feature('timeout'):
raise RuntimeError('timeout is not supported with urllib2 transport')
if proxy:
@@ -94,26 +124,26 @@ class urllib2Transport(TransportBase):
self.request_opener = urllib2.urlopen
if sessions:
from cookielib import CookieJar
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(CookieJar()))
self.request_opener = opener.open
self._timeout = timeout
def request(self, url, method="GET", body=None, headers={}):
req = urllib2.Request(url, body, headers)
try:
f = self.request_opener(req, timeout=self._timeout)
except urllib2.HTTPError, f:
return f.info(), f.read()
except urllib2.HTTPError as f:
if f.code != 500:
raise
return f.info(), f.read()
return f.info(), f.read()
_http_connectors['urllib2'] = urllib2Transport
_http_facilities.setdefault('sessions', []).append('urllib2')
import sys
if sys.version_info >= (2,6):
if sys.version_info >= (2, 6):
_http_facilities.setdefault('timeout', []).append('urllib2')
del sys
@@ -129,19 +159,23 @@ else:
try:
from cStringIO import StringIO
except ImportError:
from StringIO import StringIO
try:
from StringIO import StringIO
except ImportError:
from io import StringIO
class pycurlTransport(TransportBase):
_wrapper_version = pycurl.version
_wrapper_name = 'pycurl'
def __init__(self, timeout, proxy=None, cacert=None, sessions=False):
self.timeout = timeout
self.timeout = timeout
self.proxy = proxy or {}
self.cacert = cacert
def request(self, url, method, body, headers):
c = pycurl.Curl()
c.setopt(pycurl.URL, str(url))
c.setopt(pycurl.URL, url)
if 'proxy_host' in self.proxy:
c.setopt(pycurl.PROXY, self.proxy['proxy_host'])
if 'proxy_port' in self.proxy:
@@ -154,20 +188,19 @@ else:
#self.body = StringIO(body)
#c.setopt(pycurl.HEADERFUNCTION, self.header)
if self.cacert:
c.setopt(c.CAINFO, str(self.cacert))
c.setopt(c.CAINFO, self.cacert)
c.setopt(pycurl.SSL_VERIFYPEER, self.cacert and 1 or 0)
c.setopt(pycurl.SSL_VERIFYHOST, self.cacert and 2 or 0)
c.setopt(pycurl.CONNECTTIMEOUT, self.timeout/6)
c.setopt(pycurl.CONNECTTIMEOUT, self.timeout / 6)
c.setopt(pycurl.TIMEOUT, self.timeout)
if method=='POST':
if method == 'POST':
c.setopt(pycurl.POST, 1)
c.setopt(pycurl.POSTFIELDS, body)
c.setopt(pycurl.POSTFIELDS, body)
if headers:
hdrs = ['%s: %s' % (str(k), str(v)) for k, v in headers.items()]
##print hdrs
hdrs = ['%s: %s' % (k, v) for k, v in headers.items()]
log.debug(hdrs)
c.setopt(pycurl.HTTPHEADER, hdrs)
c.perform()
##print "pycurl perform..."
c.close()
return {}, self.buf.getvalue()
@@ -178,15 +211,15 @@ else:
class DummyTransport:
"Testing class to load a xml response"
"""Testing class to load a xml response"""
def __init__(self, xml_response):
self.xml_response = xml_response
def request(self, location, method, body, headers):
print method, location
print headers
print body
log.debug("%s %s", method, location)
log.debug(headers)
log.debug(body)
return {}, self.xml_response
@@ -222,20 +255,19 @@ def get_http_wrapper(library=None, features=[]):
else:
return _http_connectors[candidate_name]
def set_http_wrapper(library=None, features=[]):
"Set a suitable HTTP connection wrapper."
"""Set a suitable HTTP connection wrapper."""
global Http
Http = get_http_wrapper(library, features)
return Http
def get_Http():
"Return current transport class"
"""Return current transport class"""
global Http
return Http
# define the default HTTP connection class (it can be changed at runtime!):
set_http_wrapper()
+7 -3
View File
@@ -24,7 +24,7 @@ def RedisCache(*args, **vars):
Usage example: put in models
from gluon.contrib.redis_cache import RedisCache
cache.redis = RedisCache('localhost:6379',db=None, debug=True, with_lock=True)
cache.redis = RedisCache('localhost:6379',db=None, debug=True, with_lock=True, password=None)
:param db: redis db to use (0..16)
:param debug: if True adds to stats() the total_hits and misses
@@ -77,8 +77,9 @@ class RedisClient(object):
MAX_RETRIES = 5
RETRIES = 0
def __init__(self, server='localhost:6379', db=None, debug=False, with_lock=False):
def __init__(self, server='localhost:6379', db=None, debug=False, with_lock=False, password=None):
self.server = server
self.password = password
self.db = db or 0
host, port = (self.server.split(':') + ['6379'])[:2]
port = int(port)
@@ -102,7 +103,10 @@ class RedisClient(object):
self.cache_set_key = 'w2p:%s:___cache_set' % (self.request.application)
self.r_server = redis.Redis(host=host, port=port, db=self.db)
self.r_server = redis.Redis(host=host, port=port, db=self.db, password=self.password)
def initialize(self):
pass
def __call__(self, key, f, time_expire=300, with_lock=None):
if with_lock is None:
+15 -7
View File
@@ -21,7 +21,7 @@ def RedisSession(*args, **vars):
"""
Usage example: put in models
from gluon.contrib.redis_session import RedisSession
sessiondb = RedisSession('localhost:6379',db=0, session_expiry=False)
sessiondb = RedisSession('localhost:6379',db=0, session_expiry=False, password=None)
session.connect(request, response, db = sessiondb)
Simple slip-in storage for session
@@ -45,12 +45,13 @@ class RedisClient(object):
_release_script = None
def __init__(self, server='localhost:6379', db=None, debug=False,
session_expiry=False, with_lock=False):
session_expiry=False, with_lock=False, password=None):
"""session_expiry can be an integer, in seconds, to set the default expiration
of sessions. The corresponding record will be deleted from the redis instance,
and there's virtually no need to run sessions2trash.py
"""
self.server = server
self.password = password
self.db = db or 0
host, port = (self.server.split(':') + ['6379'])[:2]
port = int(port)
@@ -59,7 +60,7 @@ class RedisClient(object):
self.app = current.request.application
else:
self.app = ''
self.r_server = redis.Redis(host=host, port=port, db=self.db)
self.r_server = redis.Redis(host=host, port=port, db=self.db, password=self.password)
if with_lock:
RedisClient._release_script = \
self.r_server.register_script(_LUA_RELEASE_LOCK)
@@ -110,7 +111,7 @@ class MockTable(object):
self.session_expiry = session_expiry
self.with_lock = with_lock
def __call__(self, record_id):
def __call__(self, record_id, unique_key=None):
# Support DAL shortcut query: table(record_id)
q = self.id # This will call the __getattr__ below
@@ -119,6 +120,7 @@ class MockTable(object):
# Instructs MockQuery, to behave as db(table.id == record_id)
q.op = 'eq'
q.value = record_id
q.unique_key = unique_key
row = q.select()
return row[0] if row else Storage()
@@ -128,7 +130,7 @@ class MockTable(object):
#return a fake query. We need to query it just by id for normal operations
self.query = MockQuery(field='id', db=self.r_server,
prefix=self.keyprefix, session_expiry=self.session_expiry,
with_lock=self.with_lock)
with_lock=self.with_lock, unique_key=self.unique_key)
return self.query
elif key == '_db':
#needed because of the calls in sessions2trash.py and globals.py
@@ -161,7 +163,7 @@ class MockQuery(object):
and listing all keys. No other operation is supported
"""
def __init__(self, field=None, db=None, prefix=None, session_expiry=False,
with_lock=False):
with_lock=False, unique_key=None):
self.field = field
self.value = None
self.db = db
@@ -169,6 +171,7 @@ class MockQuery(object):
self.op = None
self.session_expiry = session_expiry
self.with_lock = with_lock
self.unique_key = unique_key
def __eq__(self, value, op='eq'):
self.value = value
@@ -186,7 +189,12 @@ class MockQuery(object):
acquire_lock(self.db, key + ':lock', self.value)
rtn = self.db.hgetall(key)
if rtn:
rtn['update_record'] = self.update # update record support
if self.unique_key:
#make sure the id and unique_key are correct
if rtn['unique_key'] == self.unique_key:
rtn['update_record'] = self.update # update record support
else:
rtn = None
return [Storage(rtn)] if rtn else []
elif self.op == 'ge' and self.field == 'id' and self.value == 0:
#means that someone wants the complete list
+1 -1
View File
@@ -258,7 +258,7 @@ def run(history, statement, env={}):
if not name.startswith('__'):
try:
history.set_global(name, val)
except TypeError, ex:
except (TypeError, cPickle.PicklingError), ex:
UNPICKLABLE_TYPES.append(type(val))
history.add_unpicklable(statement, new_globals.keys())
+6 -4
View File
@@ -81,12 +81,13 @@ class JSONSafeTransport(JSONTransportMixin, SafeTransport):
class ServerProxy(object):
"JSON RPC Simple Client Service Proxy"
def __init__(self, uri, transport=None, encoding=None, verbose=0):
def __init__(self, uri, transport=None, encoding=None, verbose=0,version=None):
self.location = uri # server location (url)
self.trace = verbose # show debug messages
self.exceptions = True # raise errors? (JSONRPCError)
self.timeout = None
self.json_request = self.json_response = ''
self.version = version # '2.0' for jsonrpc2
type, uri = urllib.splittype(uri)
if type not in ("http", "https"):
@@ -112,6 +113,8 @@ class ServerProxy(object):
# build data sent to the service
request_id = random.randint(0, sys.maxint)
data = {'id': request_id, 'method': method, 'params': args, }
if self.version:
data['jsonrpc'] = self.version #mandatory key/value for jsonrpc2 validation else err -32600
request = json.dumps(data)
# make HTTP request (retry if connection is lost)
@@ -130,14 +133,13 @@ class ServerProxy(object):
# {'version': '1.1', 'id': id, 'result': result, 'error': None}
response = json.loads(response)
if response['id'] != request_id:
raise JSONRPCError(0, "JSON Request ID != Response ID")
self.error = response.get('error', {})
if self.error and self.exceptions:
raise JSONRPCError(self.error.get('code', 0),
self.error.get('message', ''),
self.error.get('data', None))
if response['id'] != request_id:
raise JSONRPCError(0, "JSON Request ID != Response ID")
return response.get('result')
+1 -1
View File
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
"""
Developed by Massimo Di Pierro, optional component of web2py, GPL2 license.
Developed by Massimo Di Pierro, optional component of web2py, BSDv3 license.
"""
import re
+3 -2
View File
@@ -1,3 +1,4 @@
"""
Developed by Massimo Di Pierro
Released under the web2py license (LGPL)
@@ -105,9 +106,9 @@ class WebClient(object):
# assume everything is ok and make http request
error = None
try:
if isinstance(data,str):
if isinstance(data, str):
self.method = 'POST' if method=='auto' else method
if isinstance(data, dict):
elif isinstance(data, dict):
self.method = 'POST' if method=='auto' else method
# if there is only one form, set _formname automatically
if not '_formname' in data and len(self.forms) == 1:
+9 -9
View File
@@ -1,4 +1,6 @@
# -*- coding: utf8 -*-
#!/usr/bin/env python
"""
This file is part of the web2py Web Framework
Copyrighted by Massimo Di Pierro <mdip...@cs.depaul.edu>
@@ -17,13 +19,13 @@ Attention: Requires Chrome or Safari. For IE of Firefox you need https://github.
3) from any web2py app you can post messages with
from gluon.contrib.websocket_messaging import websocket_send
websocket_send('http://127.0.0.1:8888','Hello World','mykey','mygroup')
websocket_send('http://127.0.0.1:8888', 'Hello World', 'mykey', 'mygroup')
4) from any template you can receive them with
<script>
$(document).ready(function(){
if(!$.web2py.web2py_websocket('ws://127.0.0.1:8888/realtime/mygroup',function(e){alert(e.data)}))
if(!$.web2py.web2py_websocket('ws://127.0.0.1:8888/realtime/mygroup', function(e){alert(e.data)}))
alert("html5 websocket not supported by your browser, try Google Chrome");
});
@@ -35,7 +37,7 @@ Or if you want to send json messages and store evaluated json in a var called da
<script>
$(document).ready(function(){
var data;
$.web2py.web2py_websocket('ws://127.0.0.1:8888/realtime/mygroup',function(e){data=eval('('+e.data+')')});
$.web2py.web2py_websocket('ws://127.0.0.1:8888/realtime/mygroup', function(e){data=eval('('+e.data+')')});
});
</script>
@@ -49,11 +51,11 @@ Or if you want to send json messages and store evaluated json in a var called da
Here is a complete sample web2py action:
def index():
form=LOAD('default','ajax_form',ajax=True)
form=LOAD('default', 'ajax_form', ajax=True)
script=SCRIPT('''
jQuery(document).ready(function(){
var callback=function(e){alert(e.data)};
if(!$.web2py.web2py_websocket('ws://127.0.0.1:8888/realtime/mygroup',callback))
if(!$.web2py.web2py_websocket('ws://127.0.0.1:8888/realtime/mygroup', callback))
alert("html5 websocket not supported by your browser, try Google Chrome");
});
@@ -65,7 +67,7 @@ Here is a complete sample web2py action:
if form.accepts(request,session):
from gluon.contrib.websocket_messaging import websocket_send
websocket_send(
'http://127.0.0.1:8888',form.vars.message,'mykey','mygroup')
'http://127.0.0.1:8888', form.vars.message, 'mykey', 'mygroup')
return form
Acknowledgements:
@@ -83,9 +85,7 @@ import optparse
import urllib
import time
listeners = {}
names = {}
tokens = {}
listeners, names, tokens = {}, {}, {}
def websocket_send(url, message, hmac_key=None, group='default'):
+12 -6
View File
@@ -1,11 +1,17 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
| This file is part of the web2py Web Framework
| Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu>
| License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
Support for smart import syntax for web2py applications
-------------------------------------------------------
"""
import __builtin__
import os
import sys
import threading
import traceback
from gluon import current
NATIVE_IMPORTER = __builtin__.__import__
@@ -35,10 +41,10 @@ class CustomImportException(ImportError):
def custom_importer(name, globals=None, locals=None, fromlist=None, level=-1):
"""
The web2py custom importer. Like the standard Python importer but it
tries to transform import statements as something like
web2py's custom importer. It behaves like the standard Python importer but
it tries to transform import statements as something like
"import applications.app_name.modules.x".
If the import failed, fall back on naive_importer
If the import fails, it falls back on naive_importer
"""
globals = globals or {}
@@ -102,7 +108,7 @@ def custom_importer(name, globals=None, locals=None, fromlist=None, level=-1):
class TrackImporter(object):
"""
An importer tracking the date of the module files and reloading them when
they have changed.
they are changed.
"""
THREAD_LOCAL = threading.local()
@@ -143,7 +149,7 @@ class TrackImporter(object):
def _reload_check(self, name, globals, locals, level):
"""
Update the date associated to the module and reload the module if
the file has changed.
the file changed.
"""
module = sys.modules.get(name)
file = self._get_module_file(module)
+3014 -2736
View File
File diff suppressed because it is too large Load Diff
+9 -8
View File
@@ -2,11 +2,13 @@
# -*- coding: utf-8 -*-
"""
This file is part of the web2py Web Framework
Developed by Massimo Di Pierro <mdipierro@cs.depaul.edu>,
limodou <limodou@gmail.com> and srackham <srackham@gmail.com>.
License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
| This file is part of the web2py Web Framework
| Developed by Massimo Di Pierro <mdipierro@cs.depaul.edu>,
| limodou <limodou@gmail.com> and srackham <srackham@gmail.com>.
| License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
Debugger support classes
------------------------
"""
import logging
@@ -23,7 +25,7 @@ class Pipe(Queue.Queue):
Queue.Queue.__init__(self, *args, **kwargs)
def write(self, data):
logger.debug("debug %s writting %s" % (self.__name, data))
logger.debug("debug %s writing %s" % (self.__name, data))
self.put(data)
def flush(self):
@@ -183,9 +185,8 @@ parent_queue, child_queue = Queue.Queue(), Queue.Queue()
front_conn = qdb.QueuePipe("parent", parent_queue, child_queue)
child_conn = qdb.QueuePipe("child", child_queue, parent_queue)
web_debugger = WebDebugger(front_conn) # frontend
qdb_debugger = qdb.Qdb(
pipe=child_conn, redirect_stdio=False, skip=None) # backend
web_debugger = WebDebugger(front_conn) # frontend
qdb_debugger = qdb.Qdb(pipe=child_conn, redirect_stdio=False, skip=None) # backend
dbg = qdb_debugger
# enable getting context (stack, globals/locals) at interaction
+11 -8
View File
@@ -1,7 +1,8 @@
import codecs
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""Caller will hand this library a buffer and ask it to either convert
"""
Caller will hand this library a buffer and ask it to either convert
it or auto-detect the type.
Based on http://code.activestate.com/recipes/52257/
@@ -9,6 +10,8 @@ Based on http://code.activestate.com/recipes/52257/
Licensed under the PSF License
"""
import codecs
# None represents a potentially variable byte. "##" in the XML spec...
autodetect_dict = { # bytepattern : ("name",
(0x00, 0x00, 0xFE, 0xFF): ("ucs4_be"),
@@ -25,15 +28,15 @@ autodetect_dict = { # bytepattern : ("name",
def autoDetectXMLEncoding(buffer):
""" buffer -> encoding_name
The buffer should be at least 4 bytes long.
Returns None if encoding cannot be detected.
Note that encoding_name might not have an installed
decoder (e.g. EBCDIC)
Returns None if encoding cannot be detected.
Note that encoding_name might not have an installed
decoder (e.g. EBCDIC)
"""
# a more efficient implementation would not decode the whole
# buffer at once but otherwise we'd have to decode a character at
# a time looking for the quote character...that's a pain
encoding = "utf_8" # according to the XML spec, this is the default
encoding = "utf_8" # according to the XML spec, this is the default
# this code successively tries to refine the default
# whenever it fails to refine, it falls back to
# the last place encoding was set.
@@ -49,7 +52,7 @@ def autoDetectXMLEncoding(buffer):
if enc_info:
encoding = enc_info # we've got a guess... these are
#the new defaults
#the new defaults
# try to find a more precise encoding using xml declaration
secret_decoder_ring = codecs.lookup(encoding)[1]
+67 -29
View File
@@ -2,9 +2,12 @@
# -*- coding: utf-8 -*-
"""
This file is part of the web2py Web Framework
Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu>
License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
| This file is part of the web2py Web Framework
| Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu>
| License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
File operations
---------------
"""
import storage
@@ -45,7 +48,15 @@ __all__ = [
def parse_semantic(version="Version 1.99.0-rc.1+timestamp.2011.09.19.08.23.26"):
"http://semver.org/"
"""Parses a version string according to http://semver.org/ rules
Args:
version(str): the SemVer string
Returns:
tuple: Major, Minor, Patch, Release, Build Date
"""
re_version = re.compile('(\d+)\.(\d+)\.(\d+)(\-(?P<pre>[^\s+]*))?(\+(?P<build>\S*))')
m = re_version.match(version.strip().split()[-1])
if not m:
@@ -58,6 +69,15 @@ def parse_semantic(version="Version 1.99.0-rc.1+timestamp.2011.09.19.08.23.26"):
return (a, b, c, pre_release, build)
def parse_legacy(version="Version 1.99.0 (2011-09-19 08:23:26)"):
"""Parses "legacy" version string
Args:
version(str): the version string
Returns:
tuple: Major, Minor, Patch, Release, Build Date
"""
re_version = re.compile('[^\d]+ (\d+)\.(\d+)\.(\d+)\s*\((?P<datetime>.+?)\)\s*(?P<type>[a-z]+)?')
m = re_version.match(version)
a, b, c = int(m.group(1)), int(m.group(2)), int(m.group(3)),
@@ -66,13 +86,17 @@ def parse_legacy(version="Version 1.99.0 (2011-09-19 08:23:26)"):
return (a, b, c, pre_release, build)
def parse_version(version):
"""Attempts to parse SemVer, fallbacks on legacy
"""
version_tuple = parse_semantic(version)
if not version_tuple:
version_tuple = parse_legacy(version)
return version_tuple
def read_file(filename, mode='r'):
"returns content from filename, making sure to close the file explicitly on exit."
"""Returns content from filename, making sure to close the file explicitly
on exit.
"""
f = open(filename, mode)
try:
return f.read()
@@ -81,7 +105,9 @@ def read_file(filename, mode='r'):
def write_file(filename, value, mode='w'):
"writes <value> to filename, making sure to close the file explicitly on exit."
"""Writes <value> to filename, making sure to close the file
explicitly on exit.
"""
f = open(filename, mode)
try:
return f.write(value)
@@ -90,7 +116,8 @@ def write_file(filename, value, mode='w'):
def readlines_file(filename, mode='r'):
"applies .split('\n') to the output of read_file()"
"""Applies .split('\n') to the output of `read_file()`
"""
return read_file(filename, mode).split('\n')
@@ -112,8 +139,8 @@ def listdir(
maxnum = None,
):
"""
like os.listdir() but you can specify a regex pattern to filter files.
if add_dirs is True, the returned items will have the full path.
Like `os.listdir()` but you can specify a regex pattern to filter files.
If `add_dirs` is True, the returned items will have the full path.
"""
if path[-1:] != os.path.sep:
path = path + os.path.sep
@@ -122,7 +149,7 @@ def listdir(
else:
n = 0
regex = re.compile(expression)
items = []
items = []
for (root, dirs, files) in os.walk(path, topdown=True):
for dir in dirs[:]:
if dir.startswith('.'):
@@ -141,6 +168,8 @@ def listdir(
def recursive_unlink(f):
"""Deletes `f`. If it's a folder, also its contents will be deleted
"""
if os.path.isdir(f):
for s in os.listdir(f):
recursive_unlink(os.path.join(f, s))
@@ -150,8 +179,7 @@ def recursive_unlink(f):
def cleanpath(path):
"""
turns any expression/path into a valid filename. replaces / with _ and
"""Turns any expression/path into a valid filename. replaces / with _ and
removes special characters.
"""
@@ -165,6 +193,7 @@ def cleanpath(path):
def _extractall(filename, path='.', members=None):
# FIXME: this should be dropped because python 2.4 support was dropped
if not hasattr(tarfile.TarFile, 'extractall'):
from tarfile import ExtractError
@@ -172,10 +201,10 @@ def _extractall(filename, path='.', members=None):
def extractall(self, path='.', members=None):
"""Extract all members from the archive to the current working
directory and set owner, modification time and permissions on
directories afterwards. `path' specifies a different directory
to extract to. `members' is optional and must be a subset of the
list returned by getmembers().
directory and set owner, modification time and permissions on
directories afterwards. `path' specifies a different directory
to extract to. `members' is optional and must be a subset of the
list returned by getmembers().
"""
directories = []
@@ -226,8 +255,7 @@ def _extractall(filename, path='.', members=None):
def tar(file, dir, expression='^.+$', filenames=None):
"""
tars dir into file, only tars file that match expression
"""Tars dir into file, only tars file that match expression
"""
tar = tarfile.TarFile(file, 'w')
@@ -240,14 +268,21 @@ def tar(file, dir, expression='^.+$', filenames=None):
tar.close()
def untar(file, dir):
"""
untar file into dir
"""Untar file into dir
"""
_extractall(file, dir)
def w2p_pack(filename, path, compiled=False, filenames=None):
"""Packs a web2py application.
Args:
filename(str): path to the resulting archive
path(str): path to the application
compiled(bool): if `True` packs the compiled version
filenames(list): adds filenames to the archive
"""
filename = abspath(filename)
path = abspath(path)
tarname = filename + '.tar'
@@ -296,10 +331,12 @@ def w2p_unpack(filename, path, delete_tar=True):
def w2p_pack_plugin(filename, path, plugin_name):
"""Pack the given plugin into a w2p file.
Will match files at:
"""Packs the given plugin into a w2p file.
Will match files at::
<path>/*/plugin_[name].*
<path>/*/plugin_[name]/*
"""
filename = abspath(filename)
path = abspath(path)
@@ -329,9 +366,8 @@ def w2p_unpack_plugin(filename, path, delete_tar=True):
def tar_compiled(file, dir, expression='^.+$'):
"""
used to tar a compiled application.
the content of models, views, controllers is not stored in the tar file.
"""Used to tar a compiled application.
The content of models, views, controllers is not stored in the tar file.
"""
tar = tarfile.TarFile(file, 'w')
@@ -357,7 +393,7 @@ def up(path):
def get_session(request, other_application='admin'):
""" checks that user is authorized to access other_application"""
"""Checks that user is authorized to access other_application"""
if request.application == other_application:
raise KeyError
try:
@@ -370,7 +406,7 @@ def get_session(request, other_application='admin'):
return osession
def set_session(request, session, other_application='admin'):
""" checks that user is authorized to access other_application"""
"""Checks that user is authorized to access other_application"""
if request.application == other_application:
raise KeyError
session_id = request.cookies['session_id_' + other_application].value
@@ -380,7 +416,7 @@ def set_session(request, session, other_application='admin'):
def check_credentials(request, other_application='admin',
expiration=60 * 60, gae_login=True):
""" checks that user is authorized to access other_application"""
"""Checks that user is authorized to access other_application"""
if request.env.web2py_runtime_gae:
from google.appengine.api import users
if users.is_current_user_admin():
@@ -455,7 +491,9 @@ from settings import global_settings # we need to import settings here because
def abspath(*relpath, **base):
"convert relative path to absolute path based (by default) on applications_parent"
"""Converts relative path to absolute path based (by default) on
applications_parent
"""
path = os.path.join(*relpath)
gluon = base.get('gluon', False)
if os.path.isabs(path):
+170 -128
View File
@@ -2,9 +2,9 @@
# -*- coding: utf-8 -*-
"""
This file is part of the web2py Web Framework
Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu>
License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
| This file is part of the web2py Web Framework
| Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu>
| License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
Contains the classes for the global used variables:
@@ -39,6 +39,7 @@ import sys
import traceback
import threading
import cgi
import urlparse
import copy
import tempfile
from gluon.cache import CacheInRam
@@ -55,12 +56,12 @@ except ImportError:
have_minify = False
try:
import simplejson as sj #external installed library
import simplejson as sj # external installed library
except:
try:
import json as sj #standard installed library
import json as sj # standard installed library
except:
import gluon.contrib.simplejson as sj #pure python library
import gluon.contrib.simplejson as sj # pure python library
regex_session_id = re.compile('^([\w\-]+/)?[\w\-\.]+$')
@@ -76,6 +77,7 @@ less_template = '<link href="%s" rel="stylesheet/less" type="text/css" />'
css_inline = '<style type="text/css">\n%s\n</style>'
js_inline = '<script type="text/javascript">\n%s\n</script>'
# IMPORTANT:
# this is required so that pickled dict(s) and class.__dict__
# are sorted and web2py can detect without ambiguity when a session changes
@@ -83,20 +85,22 @@ class SortingPickler(Pickler):
def save_dict(self, obj):
self.write(EMPTY_DICT if self.bin else MARK+DICT)
self.memoize(obj)
self._batch_setitems([(key,obj[key]) for key in sorted(obj)])
self._batch_setitems([(key, obj[key]) for key in sorted(obj)])
SortingPickler.dispatch = copy.copy(Pickler.dispatch)
SortingPickler.dispatch[DictionaryType] = SortingPickler.save_dict
def sorting_dumps(obj, protocol=None):
file = cStringIO.StringIO()
SortingPickler(file, protocol).dump(obj)
return file.getvalue()
# END #####################################################################
def copystream_progress(request, chunk_size=10 ** 5):
"""
copies request.env.wsgi_input into request.body
Copies request.env.wsgi_input into request.body
and stores progress upload status in cache_ram
X-Progress-ID:length and X-Progress-ID:uploaded
"""
@@ -108,9 +112,9 @@ def copystream_progress(request, chunk_size=10 ** 5):
size = int(env['CONTENT_LENGTH'])
except ValueError:
raise HTTP(400, "Invalid Content-Length header")
try: # Android requires this
try: # Android requires this
dest = tempfile.NamedTemporaryFile()
except NotImplementedError: # and GAE this
except NotImplementedError: # and GAE this
dest = tempfile.TemporaryFile()
if not 'X-Progress-ID' in request.get_vars:
copystream(source, dest, size, chunk_size)
@@ -140,10 +144,11 @@ def copystream_progress(request, chunk_size=10 ** 5):
cache_ram(cache_key + ':uploaded', None)
return dest
class Request(Storage):
"""
defines the request object and the default values of its members
Defines the request object and the default values of its members
- env: environment variables, by gluon.main.wsgibase()
- cookies
@@ -155,7 +160,10 @@ class Request(Storage):
- function
- args
- extension
- now: datetime.datetime.today()
- now: datetime.datetime.now()
- utcnow : datetime.datetime.utcnow()
- is_local
- is_https
- restful()
"""
@@ -181,16 +189,20 @@ class Request(Storage):
self.is_local = False
self.global_settings = settings.global_settings
def parse_get_vars(self):
query_string = self.env.get('QUERY_STRING','')
dget = cgi.parse_qs(query_string, keep_blank_values=1)
"""Takes the QUERY_STRING and unpacks it to get_vars
"""
query_string = self.env.get('QUERY_STRING', '')
dget = urlparse.parse_qs(query_string, keep_blank_values=1) # Ref: https://docs.python.org/2/library/cgi.html#cgi.parse_qs
get_vars = self._get_vars = Storage(dget)
for (key, value) in get_vars.iteritems():
if isinstance(value,list) and len(value)==1:
if isinstance(value, list) and len(value) == 1:
get_vars[key] = value[0]
def parse_post_vars(self):
"""Takes the body of the request and unpacks it into
post_vars. application/json is also automatically parsed
"""
env = self.env
post_vars = self._post_vars = Storage()
body = self.body
@@ -213,7 +225,7 @@ class Request(Storage):
# parse POST variables on POST, PUT, BOTH only in post_vars
if (body and not is_json
and env.request_method in ('POST', 'PUT', 'DELETE', 'BOTH')):
query_string = env.pop('QUERY_STRING',None)
query_string = env.pop('QUERY_STRING', None)
dpost = cgi.FieldStorage(fp=body, environ=env, keep_blank_values=1)
try:
post_vars.update(dpost)
@@ -233,10 +245,10 @@ class Request(Storage):
if key is None:
continue # not sure why cgi.FieldStorage returns None key
dpk = dpost[key]
# if an element is not a file replace it with
# if an element is not a file replace it with
# its value else leave it alone
pvalue = listify([(_dpk if _dpk.filename else _dpk.value)
pvalue = listify([(_dpk if _dpk.filename else _dpk.value)
for _dpk in dpk]
if isinstance(dpk, list) else
(dpk if dpk.filename else dpk.value))
@@ -253,32 +265,37 @@ class Request(Storage):
return self._body
def parse_all_vars(self):
"""Merges get_vars and post_vars to vars
"""
self._vars = copy.copy(self.get_vars)
for key,value in self.post_vars.iteritems():
for key, value in self.post_vars.iteritems():
if not key in self._vars:
self._vars[key] = value
else:
if not isinstance(self._vars[key],list):
if not isinstance(self._vars[key], list):
self._vars[key] = [self._vars[key]]
self._vars[key] += value if isinstance(value,list) else [value]
self._vars[key] += value if isinstance(value, list) else [value]
@property
def get_vars(self):
"lazily parse the query string into get_vars"
"""Lazily parses the query string into get_vars
"""
if self._get_vars is None:
self.parse_get_vars()
return self._get_vars
@property
def post_vars(self):
"lazily parse the body into post_vars"
"""Lazily parse the body into post_vars
"""
if self._post_vars is None:
self.parse_post_vars()
return self._post_vars
@property
def vars(self):
"lazily parse all get_vars and post_vars to fill vars"
"""Lazily parses all get_vars and post_vars to fill vars
"""
if self._vars is None:
self.parse_all_vars()
return self._vars
@@ -306,14 +323,14 @@ class Request(Storage):
def requires_https(self):
"""
If request comes in over HTTP, redirect it to HTTPS
and secure the session.
If request comes in over HTTP, redirects it to HTTPS
and secures the session.
"""
cmd_opts = global_settings.cmd_options
#checking if this is called within the scheduler or within the shell
#in addition to checking if it's not a cronjob
if ((cmd_opts and (cmd_opts.shell or cmd_opts.scheduler))
or global_settings.cronjob or self.is_https):
or global_settings.cronjob or self.is_https):
current.session.secure()
else:
current.session.forget()
@@ -329,17 +346,17 @@ class Request(Storage):
current.response.headers['Content-Type'] = \
contenttype('.' + _self.extension.lower())
rest_action = _action().get(method, None)
if not (rest_action and method==method.upper()
if not (rest_action and method == method.upper()
and callable(rest_action)):
raise HTTP(400, "method not supported")
raise HTTP(405, "method not allowed")
try:
return rest_action(*_self.args, **getattr(_self,'vars',{}))
return rest_action(*_self.args, **getattr(_self, 'vars', {}))
except TypeError, e:
exc_type, exc_value, exc_traceback = sys.exc_info()
if len(traceback.extract_tb(exc_traceback)) == 1:
raise HTTP(400, "invalid arguments")
else:
raise e
raise
f.__doc__ = action.__doc__
f.__name__ = action.__name__
return f
@@ -349,7 +366,7 @@ class Request(Storage):
class Response(Storage):
"""
defines the response object and the default values of its members
Defines the response object and the default values of its members
response.write( ) can be used to write in the output html
"""
@@ -411,9 +428,13 @@ class Response(Storage):
return page
def include_meta(self):
s = '\n'.join(
'<meta name="%s" content="%s" />\n' % (k, xmlescape(v))
for k, v in (self.meta or {}).iteritems())
s = "\n";
for meta in (self.meta or {}).iteritems():
k,v = meta
if isinstance(v,dict):
s = s+'<meta'+''.join(' %s="%s"' % (xmlescape(key), xmlescape(v[key])) for key in v) +' />\n'
else:
s = s+'<meta name="%s" content="%s" />\n' % (k, xmlescape(v))
self.write(s, escape=False)
def include_files(self, extensions=None):
@@ -484,30 +505,33 @@ class Response(Storage):
s += js_inline % item[1]
self.write(s, escape=False)
def stream(
self,
stream,
chunk_size=DEFAULT_CHUNK_SIZE,
request=None,
attachment=False,
filename=None
):
def stream(self,
stream,
chunk_size=DEFAULT_CHUNK_SIZE,
request=None,
attachment=False,
filename=None
):
"""
if a controller function::
If in a controller function::
return response.stream(file, 100)
the file content will be streamed at 100 bytes at the time
Optional kwargs:
(for custom stream calls)
attachment=True # Send as attachment. Usually creates a
# pop-up download window on browsers
filename=None # The name for the attachment
Args:
stream: filename or read()able content
chunk_size(int): Buffer size
request: the request object
attachment(bool): prepares the correct headers to download the file
as an attachment. Usually creates a pop-up download window
on browsers
filename(str): the name for the attachment
Note: for using the stream name (filename) with attachments
the option must be explicitly set as function parameter(will
default to the last request argument otherwise)
Note:
for using the stream name (filename) with attachments
the option must be explicitly set as function parameter (will
default to the last request argument otherwise)
"""
headers = self.headers
@@ -559,12 +583,12 @@ class Response(Storage):
def download(self, request, db, chunk_size=DEFAULT_CHUNK_SIZE, attachment=True, download_filename=None):
"""
example of usage in controller::
Example of usage in controller::
def download():
return response.download(request, db)
downloads from http://..../download/filename
Downloads from http://..../download/filename
"""
current.session.forget(current.response)
@@ -572,8 +596,7 @@ class Response(Storage):
if not request.args:
raise HTTP(404)
name = request.args[-1]
items = re.compile('(?P<table>.*?)\.(?P<field>.*?)\..*')\
.match(name)
items = re.compile('(?P<table>.*?)\.(?P<field>.*?)\..*').match(name)
if not items:
raise HTTP(404)
(t, f) = (items.group('table'), items.group('field'))
@@ -582,16 +605,16 @@ class Response(Storage):
except AttributeError:
raise HTTP(404)
try:
(filename, stream) = field.retrieve(name,nameonly=True)
(filename, stream) = field.retrieve(name, nameonly=True)
except IOError:
raise HTTP(404)
headers = self.headers
headers['Content-Type'] = contenttype(name)
if download_filename == None:
if download_filename is None:
download_filename = filename
if attachment:
headers['Content-Disposition'] = \
'attachment; filename="%s"' % download_filename.replace('"','\"')
'attachment; filename="%s"' % download_filename.replace('"', '\"')
return self.stream(stream, chunk_size=chunk_size, request=request)
def json(self, data, default=None):
@@ -629,10 +652,9 @@ class Response(Storage):
dbstats = []
dbtables = {}
infos = DAL.get_instances()
for k,v in infos.iteritems():
dbstats.append(TABLE(*[TR(PRE(row[0]),'%.2fms' %
(row[1]*1000))
for row in v['dbstats']]))
for k, v in infos.iteritems():
dbstats.append(TABLE(*[TR(PRE(row[0]), '%.2fms' % (row[1]*1000))
for row in v['dbstats']]))
dbtables[k] = dict(defined=v['dbtables']['defined'] or '[no defined tables]',
lazy=v['dbtables']['lazy'] or '[no lazy tables]')
u = web2py_uuid()
@@ -641,8 +663,8 @@ class Response(Storage):
# will be displayed in the toolbar.
request = copy.copy(current.request)
request.update(vars=current.request.vars,
get_vars=current.request.get_vars,
post_vars=current.request.post_vars)
get_vars=current.request.get_vars,
post_vars=current.request.post_vars)
return DIV(
BUTTON('design', _onclick="document.location='%s'" % admin),
BUTTON('request',
@@ -671,54 +693,70 @@ class Response(Storage):
class Session(Storage):
"""
defines the session object and the default values of its members (None)
Defines the session object and the default values of its members (None)
response.session_storage_type : 'file', 'db', or 'cookie'
response.session_cookie_compression_level :
response.session_cookie_expires : cookie expiration
response.session_cookie_key : for encrypted sessions in cookies
response.session_id : a number or None if no session
response.session_id_name :
response.session_locked :
response.session_masterapp :
response.session_new : a new session obj is being created
response.session_hash : hash of the pickled loaded session
response.session_pickled : picked session
- session_storage_type : 'file', 'db', or 'cookie'
- session_cookie_compression_level :
- session_cookie_expires : cookie expiration
- session_cookie_key : for encrypted sessions in cookies
- session_id : a number or None if no session
- session_id_name :
- session_locked :
- session_masterapp :
- session_new : a new session obj is being created
- session_hash : hash of the pickled loaded session
- session_pickled : picked session
if session in cookie:
response.session_data_name : name of the cookie for session data
- session_data_name : name of the cookie for session data
if session in db:
response.session_db_record_id :
response.session_db_table :
response.session_db_unique_key :
- session_db_record_id
- session_db_table
- session_db_unique_key
if session in file:
response.session_file :
response.session_filename :
- session_file
- session_filename
"""
def connect(
self,
request=None,
response=None,
db=None,
tablename='web2py_session',
masterapp=None,
migrate=True,
separate=None,
check_client=False,
cookie_key=None,
cookie_expires=None,
compression_level=None
):
def connect(self,
request=None,
response=None,
db=None,
tablename='web2py_session',
masterapp=None,
migrate=True,
separate=None,
check_client=False,
cookie_key=None,
cookie_expires=None,
compression_level=None
):
"""
separate can be separate=lambda(session_name): session_name[-2:]
and it is used to determine a session prefix.
separate can be True and it is set to session_name[-2:]
Used in models, allows to customize Session handling
Args:
request: the request object
response: the response object
db: to store/retrieve sessions in db (a table is created)
tablename(str): table name
masterapp(str): points to another's app sessions. This enables a
"SSO" environment among apps
migrate: passed to the underlying db
separate: with True, creates a folder with the 2 initials of the
session id. Can also be a function, e.g. ::
separate=lambda(session_name): session_name[-2:]
check_client: if True, sessions can only come from the same ip
cookie_key(str): secret for cookie encryption
cookie_expires: sets the expiration of the cookie
compression_level(int): 0-9, sets zlib compression on the data
before the encryption
"""
request = request or current.request
response = response or current.response
@@ -736,7 +774,7 @@ class Session(Storage):
response.session_cookie_compression_level = compression_level
# check if there is a session_id in cookies
try:
try:
old_session_id = cookies[response.session_id_name].value
except KeyError:
old_session_id = None
@@ -752,8 +790,8 @@ class Session(Storage):
# why do we do this?
# because connect may be called twice, by web2py and in models.
# the first time there is no db yet so it should do nothing
if (global_settings.db_sessions is True or
masterapp in global_settings.db_sessions):
if (global_settings.db_sessions is True
or masterapp in global_settings.db_sessions):
return
if response.session_storage_type == 'cookie':
@@ -816,7 +854,10 @@ class Session(Storage):
# if on GAE tickets go also in DB
if settings.global_settings.web2py_runtime_gae:
request.tickets_db = db
table_migrate = (masterapp == request.application)
if masterapp == request.application:
table_migrate = migrate
else:
table_migrate = False
tname = tablename + '_' + masterapp
table = db.get(tname, None)
Field = db.Field
@@ -839,18 +880,19 @@ class Session(Storage):
try:
(record_id, unique_key) = response.session_id.split(':')
record_id = long(record_id)
except (TypeError,ValueError):
except (TypeError, ValueError):
record_id = None
# Select from database
if record_id:
row = table(record_id) #,unique_key=unique_key)
row = table(record_id, unique_key=unique_key)
# Make sure the session data exists in the database
if row:
# rows[0].update_record(locked=True)
# Unpickle the data
session_data = cPickle.loads(row.session_data)
self.update(session_data)
response.session_new = False
else:
record_id = None
if record_id:
@@ -871,7 +913,7 @@ class Session(Storage):
# yet cookie may be reset later
# Removed comparison between old and new session ids - should send
# the cookie all the time
if isinstance(response.session_id,str):
if isinstance(response.session_id, str):
response.cookies[response.session_id_name] = response.session_id
response.cookies[response.session_id_name]['path'] = '/'
if cookie_expires:
@@ -884,7 +926,6 @@ class Session(Storage):
if self.flash:
(response.flash, self.flash) = (self.flash, None)
def renew(self, clear_session=False):
if clear_session:
@@ -904,7 +945,7 @@ class Session(Storage):
self._close(response)
uuid = web2py_uuid()
response.session_id = '%s-%s' % (response.session_client, uuid)
separate = (lambda s: s[-2:]) if session and response.session_id[2:3]=="/" else None
separate = (lambda s: s[-2:]) if session and response.session_id[2:3] == "/" else None
if separate:
prefix = separate(response.session_id)
response.session_id = '%s/%s' % \
@@ -928,15 +969,15 @@ class Session(Storage):
return
(record_id, sep, unique_key) = response.session_id.partition(':')
if record_id.isdigit() and long(record_id)>0:
if record_id.isdigit() and long(record_id) > 0:
new_unique_key = web2py_uuid()
row = table(record_id)
if row and row.unique_key==unique_key:
table._db(table.id==record_id).update(unique_key=new_unique_key)
if row and row.unique_key == unique_key:
table._db(table.id == record_id).update(unique_key=new_unique_key)
else:
record_id = None
if record_id:
response.session_id = '%s:%s' % (record_id, unique_key)
response.session_id = '%s:%s' % (record_id, new_unique_key)
response.session_db_record_id = record_id
response.session_db_unique_key = new_unique_key
else:
@@ -985,7 +1026,7 @@ class Session(Storage):
rcookies[response.session_id_name] = response.session_id
rcookies[response.session_id_name]['path'] = '/'
expires = response.session_cookie_expires
if isinstance(expires,datetime.datetime):
if isinstance(expires, datetime.datetime):
expires = expires.strftime(FMT)
if expires:
rcookies[response.session_id_name]['expires'] = expires
@@ -1031,13 +1072,13 @@ class Session(Storage):
rcookies[name] = value
rcookies[name]['path'] = '/'
expires = response.session_cookie_expires
if isinstance(expires,datetime.datetime):
if isinstance(expires, datetime.datetime):
expires = expires.strftime(FMT)
if expires:
rcookies[name]['expires'] = expires
return True
def _unchanged(self,response):
def _unchanged(self, response):
session_pickled = cPickle.dumps(self)
response.session_pickled = session_pickled
session_hash = hashlib.md5(session_pickled).hexdigest()
@@ -1047,12 +1088,12 @@ class Session(Storage):
# don't save if file-based sessions,
# no session id, or session being forgotten
# or no changes to session (Unless the session is new)
if (not response.session_db_table or
self._forget or
(self._unchanged(response) and not response.session_new)):
if (not response.session_db_table and
global_settings.db_sessions is not True and
response.session_masterapp in global_settings.db_sessions):
if (not response.session_db_table
or self._forget
or (self._unchanged(response) and not response.session_new)):
if (not response.session_db_table
and global_settings.db_sessions is not True
and response.session_masterapp in global_settings.db_sessions):
global_settings.db_sessions.remove(response.session_masterapp)
# self.clear_session_cookies()
self.save_session_id_cookie()
@@ -1073,7 +1114,7 @@ class Session(Storage):
session_data=session_pickled,
unique_key=unique_key)
if record_id:
if not table._db(table.id==record_id).update(**dd):
if not table._db(table.id == record_id).update(**dd):
record_id = None
if not record_id:
record_id = table.insert(**dd)
@@ -1092,15 +1133,16 @@ class Session(Storage):
def _try_store_in_file(self, request, response):
try:
if (not response.session_id or self._forget
or self._unchanged(response)):
if (not response.session_id or self._forget
or self._unchanged(response)):
# self.clear_session_cookies()
self.save_session_id_cookie()
return False
if response.session_new or not response.session_file:
# Tests if the session sub-folder exists, if not, create it
session_folder = os.path.dirname(response.session_filename)
if not os.path.exists(session_folder): os.mkdir(session_folder)
if not os.path.exists(session_folder):
os.mkdir(session_folder)
response.session_file = open(response.session_filename, 'wb')
portalocker.lock(response.session_file, portalocker.LOCK_EX)
response.session_locked = True
+6 -7
View File
@@ -2,9 +2,9 @@
# -*- coding: utf-8 -*-
"""
This file is part of the web2py Web Framework
Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu>
License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
| This file is part of the web2py Web Framework
| Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu>
| License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
"""
import re
@@ -15,8 +15,7 @@ __all__ = ['highlight']
class Highlighter(object):
"""
Do syntax highlighting.
"""Does syntax highlighting.
"""
def __init__(
@@ -26,7 +25,7 @@ class Highlighter(object):
styles=None,
):
"""
Initialise highlighter:
Initialize highlighter:
mode = language (PYTHON, WEB2PY,C, CPP, HTML, HTML_PLAIN)
"""
styles = styles or {}
@@ -333,7 +332,7 @@ def highlight(
== '_' and value])
if fa:
fa = ' ' + fa
return '<table%s><tr style="vertical-align:top;"><td style="width:40px; text-align: right;"><pre style="%s">%s</pre></td><td><pre style="%s">%s</pre></td></tr></table>'\
return '<table%s><tr style="vertical-align:top;"><td style="min-width:40px; text-align: right;"><pre style="%s">%s</pre></td><td><pre style="%s">%s</pre></td></tr></table>'\
% (fa, linenumbers_style, numbers, code_style, code)
+338 -266
View File
File diff suppressed because it is too large Load Diff
+26 -3
View File
@@ -2,9 +2,12 @@
# -*- coding: utf-8 -*-
"""
This file is part of the web2py Web Framework
Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu>
License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
| This file is part of the web2py Web Framework
| Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu>
| License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
HTTP statuses helpers
--------------------------------------------
"""
import re
@@ -58,6 +61,18 @@ defined_status = {
regex_status = re.compile('^\d{3} [0-9A-Z ]+$')
class HTTP(Exception):
"""Raises an HTTP response
Args:
status: usually an integer. If it's a well known status code, the ERROR
message will be automatically added. A string can also be passed
as `510 Foo Bar` and in that case the status code and the error
message will be parsed accordingly
body: what to return as body. If left as is, will return the error code
and the status message in the body itself
cookies: pass cookies along (usually not needed)
headers: pass headers as usual dict mapping
"""
def __init__(
self,
@@ -136,6 +151,14 @@ class HTTP(Exception):
def redirect(location='', how=303, client_side=False):
"""Raises a redirect (303)
Args:
location: the url where to redirect
how: what HTTP status code to use when redirecting
client_side: if set to True, it triggers a reload of the entire page
when the fragment has been loaded as a component
"""
if location:
from gluon import current
loc = location.replace('\r', '%0D').replace('\n', '%0A')
+126 -93
View File
@@ -2,12 +2,13 @@
# -*- coding: utf-8 -*-
"""
This file is part of the web2py Web Framework
Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu>
License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
| This file is part of the web2py Web Framework
| Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu>
| License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
| Plural subsystem is created by Vladyslav Kozlovskyy (Ukraine) <dbdevelop@gmail.com>
Plural subsystem is created by Vladyslav Kozlovskyy (Ukraine)
<dbdevelop@gmail.com>
Translation system
--------------------------------------------
"""
import os
@@ -27,7 +28,6 @@ from gluon.portalocker import read_locked, LockedFile
from utf8 import Utf8
from gluon.fileutils import listdir
import gluon.settings as settings
from gluon.cfs import getcfs
from gluon.html import XML, xmlescape
from gluon.contrib.markmin.markmin2html import render, markmin_escape
@@ -41,7 +41,6 @@ pjoin = os.path.join
pexists = os.path.exists
pdirname = os.path.dirname
isdir = os.path.isdir
is_gae = False # settings.global_settings.web2py_runtime_gae
DEFAULT_LANGUAGE = 'en'
DEFAULT_LANGUAGE_NAME = 'English'
@@ -76,6 +75,10 @@ regex_plural_tuple = re.compile(
'^{(?P<w>[^[\]()]+)(?:\[(?P<i>\d+)\])?}$') # %%{word[index]} or %%{word}
regex_plural_file = re.compile('^plural-[a-zA-Z]{2}(-[a-zA-Z]{2})?\.py$')
def is_writable():
""" returns True if and only if the filesystem is writable """
from gluon.settings import global_settings
return not global_settings.web2py_runtime_gae
def safe_eval(text):
if text.strip():
@@ -165,7 +168,7 @@ def read_dict_aux(filename):
def read_dict(filename):
""" return dictionary with translation messages
""" Returns dictionary with translation messages
"""
return getcfs('lang:' + filename, filename,
lambda: read_dict_aux(filename))
@@ -173,8 +176,8 @@ def read_dict(filename):
def read_possible_plural_rules():
"""
create list of all possible plural rules files
result is cached in PLURAL_RULES dictionary to increase speed
Creates list of all possible plural rules files
The result is cached in PLURAL_RULES dictionary to increase speed
"""
plurals = {}
try:
@@ -294,41 +297,49 @@ def read_plural_dict(filename):
def write_plural_dict(filename, contents):
if '__corrupted__' in contents:
return
fp = None
try:
fp = LockedFile(filename, 'w')
fp.write('#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n{\n# "singular form (0)": ["first plural form (1)", "second plural form (2)", ...],\n')
for key in sorted(contents, lambda x, y: cmp(unicode(x, 'utf-8').lower(), unicode(y, 'utf-8').lower())):
for key in sorted(contents, sort_function):
forms = '[' + ','.join([repr(Utf8(form))
for form in contents[key]]) + ']'
for form in contents[key]]) + ']'
fp.write('%s: %s,\n' % (repr(Utf8(key)), forms))
fp.write('}\n')
except (IOError, OSError):
if not is_gae:
if is_writable():
logging.warning('Unable to write to file %s' % filename)
return
finally:
fp.close()
if fp:
fp.close()
def sort_function(x,y):
return cmp(unicode(x, 'utf-8').lower(), unicode(y, 'utf-8').lower())
def write_dict(filename, contents):
if '__corrupted__' in contents:
return
fp = None
try:
fp = LockedFile(filename, 'w')
fp.write('# -*- coding: utf-8 -*-\n{\n')
for key in sorted(contents, sort_function):
fp.write('%s: %s,\n' % (repr(Utf8(key)),
repr(Utf8(contents[key]))))
fp.write('}\n')
except (IOError, OSError):
if not settings.global_settings.web2py_runtime_gae:
if is_writable():
logging.warning('Unable to write to file %s' % filename)
return
fp.write('# -*- coding: utf-8 -*-\n{\n')
for key in sorted(contents, lambda x, y: cmp(unicode(x, 'utf-8').lower(), unicode(y, 'utf-8').lower())):
fp.write('%s: %s,\n' % (repr(Utf8(key)), repr(Utf8(contents[key]))))
fp.write('}\n')
fp.close()
finally:
if fp:
fp.close()
class lazyT(object):
"""
never to be called explicitly, returned by
Never to be called explicitly, returned by
translator.__call__() or translator.M()
"""
m = s = T = f = t = None
@@ -428,28 +439,27 @@ copy_reg.pickle(lazyT, pickle_lazyT)
class translator(object):
"""
this class is instantiated by gluon.compileapp.build_environment
This class is instantiated by gluon.compileapp.build_environment
as the T object
::
Example:
T.force(None) # turns off translation
T.force('fr, it') # forces web2py to translate using fr.py or it.py
T(\"Hello World\") # translates \"Hello World\" using the selected file
T("Hello World") # translates "Hello World" using the selected file
notice 1: there is no need to force since, by default, T uses
http_accept_language to determine a translation file.
notice 2:
en and en-en are considered different languages!
notice 3:
if language xx-yy is not found force() probes other similar
languages using such algorithm:
xx-yy.py -> xx.py -> xx-yy*.py -> xx*.py
Note:
- there is no need to force since, by default, T uses
http_accept_language to determine a translation file.
- en and en-en are considered different languages!
- if language xx-yy is not found force() probes other similar languages
using such algorithm: `xx-yy.py -> xx.py -> xx-yy*.py -> xx*.py`
"""
def __init__(self, langpath, http_accept_language):
self.langpath = langpath
self.http_accept_language = http_accept_language
self.is_writable = not is_gae
# filled in self.force():
#------------------------
# self.cache
@@ -473,25 +483,27 @@ class translator(object):
self.otherTs = {}
self.filter = markmin
self.ftag = 'markmin'
self.ns = None
self.is_writable = True
def get_possible_languages_info(self, lang=None):
"""
return info for selected language or dictionary with all
possible languages info from APP/languages/*.py
args:
*lang* (str): language
returns:
if *lang* is defined:
return tuple(langcode, langname, langfile_mtime,
pluraldict_fname, pluraldict_mtime,
prules_langcode, nplurals,
get_plural_id, construct_plural_form)
or None
Returns info for selected language or dictionary with all
possible languages info from `APP/languages/*.py`
It Returns:
- a tuple containing::
langcode, langname, langfile_mtime,
pluraldict_fname, pluraldict_mtime,
prules_langcode, nplurals,
get_plural_id, construct_plural_form
or None
- if *lang* is NOT defined a dictionary with all possible
languages::
if *lang* is NOT defined:
returns dictionary with all possible languages:
{ langcode(from filename):
( langcode, # language code from !langcode!
langname,
@@ -504,6 +516,10 @@ class translator(object):
get_plural_id, # get_plural_id() for current language
construct_plural_form) # construct_plural_form() for current language
}
Args:
lang (str): language
"""
info = read_possible_languages(self.langpath)
if lang:
@@ -511,19 +527,18 @@ class translator(object):
return info
def get_possible_languages(self):
""" get list of all possible languages for current applications """
""" Gets list of all possible languages for current application """
return list(set(self.current_languages +
[lang for lang in read_possible_languages(self.langpath).iterkeys()
if lang != 'default']))
def set_current_languages(self, *languages):
"""
set current AKA "default" languages
setting one of this languages makes force() function
turn translation off to use default language
Sets current AKA "default" languages
Setting one of this languages makes the force() function to turn
translation off
"""
if len(languages) == 1 and isinstance(
languages[0], (tuple, list)):
if len(languages) == 1 and isinstance(languages[0], (tuple, list)):
languages = languages[0]
if not languages or languages[0] is None:
# set default language from default.py/DEFAULT_LANGUAGE
@@ -543,17 +558,20 @@ class translator(object):
self.force(self.http_accept_language)
def plural(self, word, n):
""" get plural form of word for number *n*
NOTE: *word" MUST be defined in current language
(T.accepted_language)
"""
Gets plural form of word for number *n*
invoked from T()/T.M() in `%%{}` tag
invoked from T()/T.M() in %%{} tag
args:
Note:
"word" MUST be defined in current language (T.accepted_language)
Args:
word (str): word in singular
n (numeric): number plural form created for
returns:
(str): word in appropriate singular/plural form
Returns:
word (str): word in appropriate singular/plural form
"""
if int(n) == 1:
return word
@@ -574,7 +592,7 @@ class translator(object):
form = self.construct_plural_form(word, id)
forms[id - 1] = form
self.plural_dict[word] = forms
if self.is_writable and self.plural_file:
if self.is_writable and is_writable() and self.plural_file:
write_plural_dict(self.plural_file,
self.plural_dict)
return form
@@ -582,11 +600,10 @@ class translator(object):
def force(self, *languages):
"""
select language(s) for translation
Selects language(s) for translation
if a list of languages is passed as a parameter,
first language from this list that matches the ones
the first language from this list that matches the ones
from the possible_languages dictionary will be
selected
@@ -658,7 +675,12 @@ class translator(object):
set_plural(language)
self.accepted_language = language
return languages
self.accepted_language = language or self.current_languages[0]
self.accepted_language = language
if not language:
if self.current_languages:
self.accepted_language = self.current_languages[0]
else:
self.accepted_language = DEFAULT_LANGUAGE
self.language_file = self.default_language_file
self.cache = global_language_cache.setdefault(self.language_file,
({}, RLock()))
@@ -686,11 +708,11 @@ class translator(object):
self.ns = ns
otherT = self.__get_otherT__(language, ns)
return otherT(message, symbols, lazy=lazy)
def __get_otherT__(self, language=None, namespace=None):
if not language and not namespace:
raise Exception('Incorrect parameters')
if namespace:
if language:
index = '%s/%s' % (namespace, language)
@@ -737,7 +759,7 @@ class translator(object):
def M(self, message, symbols={}, language=None,
lazy=None, filter=None, ftag=None, ns=None):
"""
get cached translated markmin-message with inserted parametes
Gets cached translated markmin-message with inserted parametes
if lazy==True lazyT object is returned
"""
if lazy is None:
@@ -755,16 +777,16 @@ class translator(object):
def get_t(self, message, prefix=''):
"""
user ## to add a comment into a translation string
Use ## to add a comment into a translation string
the comment can be useful do discriminate different possible
translations for the same string (for example different locations)
translations for the same string (for example different locations)::
T(' hello world ') -> ' hello world '
T(' hello world ## token') -> ' hello world '
T('hello ## world## token') -> 'hello ## world'
T(' hello world ') -> ' hello world '
T(' hello world ## token') -> ' hello world '
T('hello ## world## token') -> 'hello ## world'
the ## notation is ignored in multiline strings and strings that
start with ##. this is to allow markmin syntax to be translated
start with ##. This is needed to allow markmin syntax to be translated
"""
if isinstance(message, unicode):
message = message.encode('utf8')
@@ -781,32 +803,38 @@ class translator(object):
# guess translation same as original
self.t[key] = mt = self.default_t.get(key, message)
# update language file for latter translation
if self.is_writable and self.language_file != self.default_language_file:
if self.is_writable and is_writable() and \
self.language_file != self.default_language_file:
write_dict(self.language_file, self.t)
return regex_backslash.sub(
lambda m: m.group(1).translate(ttab_in), mt)
def params_substitution(self, message, symbols):
"""
substitute parameters from symbols into message using %.
also parse %%{} placeholders for plural-forms processing.
returns: string with parameters
NOTE: *symbols* MUST BE OR tuple OR dict of parameters!
Substitutes parameters from symbols into message using %.
also parse `%%{}` placeholders for plural-forms processing.
Returns:
string with parameters
Note:
*symbols* MUST BE OR tuple OR dict of parameters!
"""
def sub_plural(m):
"""string in %{} is transformed by this rules:
If string starts with \\, ! or ? such transformations
take place:
"""String in `%{}` is transformed by this rules:
If string starts with `\\`, `!` or `?` such transformations
take place::
"!string of words" -> "String of word" (Capitalize)
"!!string of words" -> "String Of Word" (Title)
"!!!string of words" -> "STRING OF WORD" (Upper)
"\\!string of words" -> "!string of word"
(remove \\ and disable transformations)
"?word?number" -> "word" (return word, if number == 1)
"?number" or "??number" -> "" (remove number,
if number == 1)
"?word?number" -> "number" (if number != 1)
"!string of words" -> "String of word" (Capitalize)
"!!string of words" -> "String Of Word" (Title)
"!!!string of words" -> "STRING OF WORD" (Upper)
"\\!string of words" -> "!string of word"
(remove \\ and disable transformations)
"?word?number" -> "word" (return word, if number == 1)
"?number" or "??number" -> "" (remove number,
if number == 1)
"?word?number" -> "number" (if number != 1)
"""
def sub_tuple(m):
""" word[number], !word[number], !!word[number], !!!word[number]
@@ -889,7 +917,7 @@ class translator(object):
def translate(self, message, symbols):
"""
get cached translated message with inserted parameters(symbols)
Gets cached translated message with inserted parameters(symbols)
"""
message = get_from_cache(self.cache, message,
lambda: self.get_t(message))
@@ -912,7 +940,8 @@ class translator(object):
def findT(path, language=DEFAULT_LANGUAGE):
"""
must be run by the admin app
Note:
Must be run by the admin app
"""
lang_file = pjoin(path, 'languages', language + '.py')
sentences = read_dict(lang_file)
@@ -949,6 +978,10 @@ def findT(path, language=DEFAULT_LANGUAGE):
write_dict(lang_file, sentences)
def update_all_languages(application_path):
"""
Note:
Must be run by the admin app
"""
path = pjoin(application_path, 'languages/')
for language in oslistdir(path):
if regex_langfile.match(language):
+25 -27
View File
@@ -2,14 +2,12 @@
# -*- coding: utf-8 -*-
"""
This file is part of the web2py Web Framework
Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu>
License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
Contains:
- wsgibase: the gluon wsgi application
| This file is part of the web2py Web Framework
| Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu>
| License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
The gluon wsgi application
---------------------------
"""
if False: import import_all # DO NOT REMOVE PART OF FREEZE PROCESS
@@ -99,7 +97,7 @@ from gluon.dal import BaseAdapter
from gluon.validators import CRYPT
from gluon.html import URL, xmlescape
from gluon.utils import is_valid_ip_address, getipaddrinfo
from gluon.rewrite import load, url_in, THREAD_LOCAL as rwthread, \
from gluon.rewrite import load as load_routes, url_in, THREAD_LOCAL as rwthread, \
try_rewrite_on_error, fixup_missing_path_info
from gluon import newcron
@@ -128,16 +126,16 @@ except:
if not global_settings.web2py_runtime_gae:
logger.warn('unable to import Rocket')
load()
load_routes()
HTTPS_SCHEMES = set(('https', 'HTTPS'))
def get_client(env):
"""
guess the client address from the environment variables
Guesses the client address from the environment variables
first tries 'http_x_forwarded_for', secondly 'remote_addr'
First tries 'http_x_forwarded_for', secondly 'remote_addr'
if all fails, assume '127.0.0.1' or '::1' (running locally)
"""
eget = env.get
@@ -160,7 +158,7 @@ def get_client(env):
def serve_controller(request, response, session):
"""
this function is used to generate a dynamic page.
This function is used to generate a dynamic page.
It first runs all models, then runs the function in the controller,
and then tries to render the output using a view/template.
this function must run from the [application] folder.
@@ -234,7 +232,7 @@ class LazyWSGI(object):
return self._environ
def start_response(self,status='200', headers=[], exec_info=None):
"""
in controller you can use::
in controller you can use:
- request.wsgi.environ
- request.wsgi.start_response
@@ -249,7 +247,7 @@ class LazyWSGI(object):
"""
In you controller use::
@request.wsgi.middleware(middleware1, middleware2, ...)
@request.wsgi.middleware(middleware1, middleware2, ...)
to decorate actions with WSGI middleware. actions must return strings.
uses a simulated environment so it may have weird behavior in some cases
@@ -271,9 +269,9 @@ class LazyWSGI(object):
def wsgibase(environ, responder):
"""
this is the gluon wsgi application. the first function called when a page
is requested (static or dynamic). it can be called by paste.httpserver
or by apache mod_wsgi.
The gluon wsgi application. The first function called when a page
is requested (static or dynamic). It can be called by paste.httpserver
or by apache mod_wsgi (or any WSGI-compatible server).
- fills request with info
- the environment variables, replacing '.' with '_'
@@ -290,13 +288,11 @@ def wsgibase(environ, responder):
2. for dynamic pages:
- /<application>[/<controller>[/<function>[/<sub>]]][.<extension>]
- (sub may go several levels deep, currently 3 levels are supported:
sub1/sub2/sub3)
The naming conventions are:
- application, controller, function and extension may only contain
[a-zA-Z0-9_]
`[a-zA-Z0-9_]`
- file and sub may also contain '-', '=', '.' and '/'
"""
eget = environ.get
@@ -567,14 +563,14 @@ def wsgibase(environ, responder):
def save_password(password, port):
"""
used by main() to save the password in the parameters_port.py file.
Used by main() to save the password in the parameters_port.py file.
"""
password_file = abspath('parameters_%i.py' % port)
if password == '<random>':
# make up a new password
chars = string.letters + string.digits
password = ''.join([random.choice(chars) for i in range(8)])
password = ''.join([random.choice(chars) for _ in range(8)])
cpassword = CRYPT()(password)[0]
print '******************* IMPORTANT!!! ************************'
print 'your admin password is "%s"' % password
@@ -607,10 +603,10 @@ def appfactory(wsgiapp=wsgibase,
generates a wsgi application that does logging and profiling and calls
wsgibase
.. function:: gluon.main.appfactory(
[wsgiapp=wsgibase
[, logfilename='httpserver.log'
[, profilerfilename='profiler.log']]])
Args:
wsgiapp: the base application
logfilename: where to store apache-compatible requests log
profiler_dir: where to store profile files
"""
if profilerfilename is not None:
@@ -729,7 +725,9 @@ class HttpServer(object):
web2py_path = path
global_settings.applications_parent = path
os.chdir(path)
[add_path_first(p) for p in (path, abspath('site-packages'), "")]
load_routes()
for p in (path, abspath('site-packages'), ""):
add_path_first(p)
if exists("logging.conf"):
logging.config.fileConfig("logging.conf")
+8 -3
View File
@@ -2,9 +2,12 @@
# -*- coding: utf-8 -*-
"""
This file is part of the web2py Web Framework
Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu>
License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
| This file is part of the web2py Web Framework
| Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu>
| License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
Useful regexes
---------------
"""
import re
@@ -21,6 +24,8 @@ regex_expose = re.compile(
'^def\s+(?P<name>_?[a-zA-Z0-9]\w*)\( *\)\s*:',
flags=re.M)
regex_longcomments = re.compile('(""".*?"""|'+"'''.*?''')",re.DOTALL)
regex_include = re.compile(
'(?P<all>\{\{\s*include\s+[\'"](?P<name>[^\'"]*)[\'"]\s*\}\})')
+23 -11
View File
@@ -2,8 +2,12 @@
# -*- coding: utf-8 -*-
"""
Created by Attila Csipa <web2py@csipa.in.rs>
Modified by Massimo Di Pierro <mdipierro@cs.depaul.edu>
| This file is part of the web2py Web Framework
| Created by Attila Csipa <web2py@csipa.in.rs>
| Modified by Massimo Di Pierro <mdipierro@cs.depaul.edu>
| License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
Cron-style interface
"""
import sys
@@ -27,7 +31,7 @@ _cron_subprocs = []
def absolute_path_link(path):
"""
Return an absolute path for the destination of a symlink
Returns an absolute path for the destination of a symlink
"""
if os.path.islink(path):
@@ -40,11 +44,17 @@ def absolute_path_link(path):
def stopcron():
"graceful shutdown of cron"
"Graceful shutdown of cron"
global _cron_stopping
_cron_stopping = True
while _cron_subprocs:
_cron_subprocs.pop().terminate()
proc = _cron_subprocs.pop()
if proc.poll() is None:
try:
proc.terminate()
except:
import traceback
traceback.print_exc()
class extcron(threading.Thread):
@@ -107,7 +117,7 @@ class Token(object):
def acquire(self, startup=False):
"""
returns the time when the lock is acquired or
Returns the time when the lock is acquired or
None if cron already running
lock is implemented by writing a pickle (start, stop) in cron.master
@@ -150,8 +160,7 @@ class Token(object):
def release(self):
"""
this function writes into cron.master the time when cron job
was completed
Writes into cron.master the time when cron job was completed
"""
if not self.master.closed:
portalocker.lock(self.master, portalocker.LOCK_EX)
@@ -251,7 +260,10 @@ class cronlauncher(threading.Thread):
shell=self.shell)
_cron_subprocs.append(proc)
(stdoutdata, stderrdata) = proc.communicate()
_cron_subprocs.remove(proc)
try:
_cron_subprocs.remove(proc)
except ValueError:
pass
if proc.returncode != 0:
logger.warning(
'WEB2PY CRON Call returned code %s:\n%s' %
@@ -317,8 +329,8 @@ def crondance(applications_parent, ctype='soft', startup=False, apps=None):
w2p_path = fileutils.abspath('web2py.py', gluon=True)
if os.path.exists(w2p_path):
commands.append(w2p_path)
if global_settings.applications_parent != global_settings.gluon_parent:
commands.extend(('-f', global_settings.applications_parent))
if applications_parent != global_settings.gluon_parent:
commands.extend(('-f', applications_parent))
citems = [(k in task and not v in task[k]) for k, v in checks]
task_min = task.get('min', [])
if not task:
+5 -7
View File
@@ -1,13 +1,10 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# portalocker.py
# Cross-platform (posix/nt) API for flock-style file locking.
# Requires python 1.5.2 or better.
"""
Cross-platform (posix/nt) API for flock-style file locking.
Synopsis:
Synopsis::
import portalocker
file = open(\"somefile\", \"r+\")
@@ -16,18 +13,18 @@ Synopsis:
file.write(\"foo\")
file.close()
If you know what you're doing, you may choose to
If you know what you're doing, you may choose to::
portalocker.unlock(file)
before closing the file, but why?
Methods:
Methods::
lock( file, flags )
unlock( file )
Constants:
Constants::
LOCK_EX
LOCK_SH
@@ -163,6 +160,7 @@ def write_locked(filename, data):
fp.close()
if __name__ == '__main__':
import sys
f = LockedFile('test.txt', mode='wb')
f.write('test ok')
f.close()
+1
View File
@@ -22,6 +22,7 @@ __author__ = "Thadeus Burgess <thadeusb@thadeusb.com>"
# These are keywords that are common to all SQL dialects, and should
# never be used as a table or column. Even if you use one of these
# the cursor will throw an OperationalError for the SQL syntax.
COMMON = set((
'SELECT',
'INSERT',
+19 -17
View File
@@ -2,9 +2,12 @@
# -*- coding: utf-8 -*-
"""
This file is part of the web2py Web Framework
Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu>
License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
| This file is part of the web2py Web Framework
| Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu>
| License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
Restricted environment to execute application's code
-----------------------------------------------------
"""
import sys
@@ -26,7 +29,7 @@ __all__ = ['RestrictedError', 'restricted', 'TicketStorage', 'compile2']
class TicketStorage(Storage):
"""
defines the ticket object and the default values of its members (None)
Defines the ticket object and the default values of its members (None)
"""
def __init__(
@@ -40,7 +43,7 @@ class TicketStorage(Storage):
def store(self, request, ticket_id, ticket_data):
"""
stores the ticket. It will figure out if this must be on disk or in db
Stores the ticket. It will figure out if this must be on disk or in db
"""
if self.db:
self._store_in_db(request, ticket_id, ticket_data)
@@ -51,12 +54,12 @@ class TicketStorage(Storage):
self.db._adapter.reconnect()
try:
table = self._get_table(self.db, self.tablename, request.application)
id = table.insert(ticket_id=ticket_id,
table.insert(ticket_id=ticket_id,
ticket_data=cPickle.dumps(ticket_data),
created_datetime=request.now)
self.db.commit()
message = 'In FILE: %(layer)s\n\n%(traceback)s\n'
except Exception, e:
except Exception:
self.db.rollback()
message =' Unable to store in FILE: %(layer)s\n\n%(traceback)s\n'
self.db.close()
@@ -111,8 +114,8 @@ class TicketStorage(Storage):
class RestrictedError(Exception):
"""
class used to wrap an exception that occurs in the restricted environment
below. the traceback is used to log the exception and generate a ticket.
Class used to wrap an exception that occurs in the restricted environment
below. The traceback is used to log the exception and generate a ticket.
"""
def __init__(
@@ -123,7 +126,7 @@ class RestrictedError(Exception):
environment=None,
):
"""
layer here is some description of where in the system the exception
Layer here is some description of where in the system the exception
occurred.
"""
if environment is None:
@@ -148,7 +151,7 @@ class RestrictedError(Exception):
def log(self, request):
"""
logs the exception.
Logs the exception.
"""
try:
@@ -168,7 +171,7 @@ class RestrictedError(Exception):
def load(self, request, app, ticket_id):
"""
loads a logged exception.
Loads a logged exception.
"""
ticket_storage = TicketStorage(db=request.tickets_db)
d = ticket_storage.load(request, app, ticket_id)
@@ -194,15 +197,16 @@ class RestrictedError(Exception):
def compile2(code, layer):
"""
The +'\n' is necessary else compile fails when code ends in a comment.
The ``+'\\n'`` is necessary else compile fails when code ends in a comment.
"""
return compile(code.rstrip().replace('\r\n', '\n') + '\n', layer, 'exec')
def restricted(code, environment=None, layer='Unknown'):
"""
runs code in environment and returns the output. if an exception occurs
in code it raises a RestrictedError containing the traceback. layer is
Runs code in environment and returns the output. If an exception occurs
in code it raises a RestrictedError containing the traceback. Layer is
passed to RestrictedError to identify where the error occurred.
"""
if environment is None:
@@ -232,8 +236,6 @@ def restricted(code, environment=None, layer='Unknown'):
def snapshot(info=None, context=5, code=None, environment=None):
"""Return a dict describing a given traceback (based on cgitb.text)."""
import os
import types
import time
import linecache
import inspect
+62 -62
View File
@@ -2,9 +2,9 @@
# -*- coding: utf-8 -*-
"""
This file is part of the web2py Web Framework
Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu>
License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
| This file is part of the web2py Web Framework
| Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu>
| License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
gluon.rewrite parses incoming URLs and formats outgoing URLs for gluon.html.URL.
@@ -57,7 +57,7 @@ regex_args = re.compile('[^\w/.@=-]')
def _router_default():
"return new copy of default base router"
"Returns new copy of default base router"
router = Storage(
default_application='init',
applications='ALL',
@@ -85,7 +85,7 @@ def _router_default():
def _params_default(app=None):
"return new copy of default parameters"
"Returns a new copy of default parameters"
p = Storage()
p.name = app or "BASE"
p.default_application = app or "init"
@@ -181,18 +181,18 @@ def fixup_missing_path_info(environ):
def url_in(request, environ):
"parse and rewrite incoming URL"
"Parses and rewrites incoming URL"
if routers:
return map_url_in(request, environ)
return regex_url_in(request, environ)
def url_out(request, environ, application, controller, function,
args, other, scheme, host, port):
"assemble and rewrite outgoing URL"
args, other, scheme, host, port, language=None):
"Assembles and rewrites outgoing URL"
if routers:
acf = map_url_out(request, environ, application, controller,
function, args, other, scheme, host, port)
function, args, other, scheme, host, port, language)
url = '%s%s' % (acf, other)
else:
url = '/%s/%s/%s%s' % (application, controller, function, other)
@@ -214,7 +214,7 @@ def url_out(request, environ, application, controller, function,
def try_rewrite_on_error(http_response, request, environ, ticket=None):
"""
called from main.wsgibase to rewrite the http response.
Called from main.wsgibase to rewrite the http response.
"""
status = int(str(http_response.status).split()[0])
if status >= 399 and THREAD_LOCAL.routes.routes_onerror:
@@ -255,7 +255,7 @@ def try_rewrite_on_error(http_response, request, environ, ticket=None):
def try_redirect_on_error(http_object, request, ticket=None):
"called from main.wsgibase to rewrite the http response"
"Called from main.wsgibase to rewrite the http response"
status = int(str(http_object.status).split()[0])
if status > 399 and THREAD_LOCAL.routes.routes_onerror:
keys = set(('%s/%s' % (request.application, status),
@@ -392,7 +392,7 @@ def load(routes='routes.py', app=None, data=None, rdict=None):
def compile_regex(k, v, env=None):
"""
Preprocess and compile the regular expressions in routes_app/in/out
The resulting regex will match a pattern of the form:
The resulting regex will match a pattern of the form::
[remote address]:[protocol]://[host]:[method] [path]
@@ -427,7 +427,7 @@ def compile_regex(k, v, env=None):
def load_routers(all_apps):
"load-time post-processing of routers"
"Load-time post-processing of routers"
for app in routers:
# initialize apps with routers that aren't present,
@@ -533,7 +533,7 @@ def load_routers(all_apps):
def regex_uri(e, regexes, tag, default=None):
"filter incoming URI against a list of regexes"
"Filters incoming URI against a list of regexes"
path = e['PATH_INFO']
host = e.get('HTTP_HOST', e.get('SERVER_NAME', 'localhost')).lower()
i = host.find(':')
@@ -555,7 +555,7 @@ def regex_uri(e, regexes, tag, default=None):
def regex_select(env=None, app=None, request=None):
"""
select a set of regex rewrite params for the current request
Selects a set of regex rewrite params for the current request
"""
if app:
THREAD_LOCAL.routes = params_apps.get(app, params)
@@ -572,7 +572,7 @@ def regex_select(env=None, app=None, request=None):
def regex_filter_in(e):
"regex rewrite incoming URL"
"Regex rewrite incoming URL"
routes = THREAD_LOCAL.routes
query = e.get('QUERY_STRING', None)
e['WEB2PY_ORIGINAL_URI'] = e['PATH_INFO'] + (query and ('?' + query) or '')
@@ -603,7 +603,7 @@ def invalid_url(routes):
web2py_error='invalid path')
def regex_url_in(request, environ):
"rewrite and parse incoming URL"
"Rewrites and parses incoming URL"
# ##################################################
# select application
@@ -667,7 +667,7 @@ def regex_url_in(request, environ):
def regex_filter_out(url, e=None):
"regex rewrite outgoing URL"
"Regex rewrite outgoing URL"
if not hasattr(THREAD_LOCAL, 'routes'):
regex_select() # ensure routes is set (for application threads)
routes = THREAD_LOCAL.routes
@@ -698,7 +698,7 @@ def regex_filter_out(url, e=None):
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):
host=None, port=None, language=None):
"""
doctest/unittest interface to regex_filter_in() and regex_filter_out()
"""
@@ -755,7 +755,7 @@ def filter_url(url, method='get', remote='0.0.0.0',
if not routers:
return regex_filter_out(uri, e)
acf = map_url_out(
request, None, a, c, f, items, None, scheme, host, port)
request, None, a, c, f, items, None, scheme, host, port, language=language)
if items:
url = '%s/%s' % (acf, '/'.join(items))
if items[-1] == '':
@@ -810,10 +810,10 @@ def filter_err(status, application='app', ticket='tkt'):
class MapUrlIn(object):
"logic for mapping incoming URLs"
"Logic for mapping incoming URLs"
def __init__(self, request=None, env=None):
"initialize a map-in object"
"Initializes a map-in object"
self.request = request
self.env = env
@@ -861,7 +861,7 @@ class MapUrlIn(object):
self.port = '443' if self.scheme == 'https' else '80'
def map_prefix(self):
"strip path prefix, if present in its entirety"
"Strips path prefix, if present in its entirety"
prefix = routers.BASE.path_prefix
if prefix:
prefixlen = len(prefix)
@@ -873,7 +873,7 @@ class MapUrlIn(object):
self.args = List(self.args[prefixlen:]) # strip the prefix
def map_app(self):
"determine application name"
"Determines application name"
base = routers.BASE # base router
self.domain_application = None
self.domain_controller = None
@@ -935,12 +935,12 @@ class MapUrlIn(object):
self._args_match = self.router._args_match
def map_root_static(self):
'''
handle root-static files (no hyphen mapping)
"""
Handles root-static files (no hyphen mapping)
a root-static file is one whose incoming URL expects it to be at the root,
typically robots.txt & favicon.ico
'''
"""
if len(self.args) == 1 and self.arg0 in self.router.root_static:
self.controller = self.request.controller = 'static'
@@ -952,7 +952,7 @@ class MapUrlIn(object):
return None, None
def map_language(self):
"handle language (no hyphen mapping)"
"Handles language (no hyphen mapping)"
arg0 = self.arg0 # no hyphen mapping
if arg0 and self.languages and arg0 in self.languages:
self.language = arg0
@@ -964,7 +964,7 @@ class MapUrlIn(object):
arg0 = self.arg0
def map_controller(self):
"identify controller"
"Identifies controller"
# handle controller
#
arg0 = self.harg0 # map hyphens
@@ -981,7 +981,7 @@ class MapUrlIn(object):
def map_static(self):
'''
handle static files
Handles static files
file_match but no hyphen mapping
'''
if self.controller != 'static':
@@ -1025,7 +1025,7 @@ class MapUrlIn(object):
return static_file, version
def map_function(self):
"handle function.extension"
"Handles function.extension"
arg0 = self.harg0 # map hyphens
functions = self.functions.get(self.controller, set())
if isinstance(self.router.default_function, dict):
@@ -1058,9 +1058,9 @@ class MapUrlIn(object):
web2py_error='invalid extension')
def validate_args(self):
'''
check args against validation pattern
'''
"""
Checks args against validation pattern
"""
for arg in self.args:
if not self.router._args_match.match(arg):
raise HTTP(
@@ -1068,16 +1068,15 @@ class MapUrlIn(object):
web2py_error='invalid arg <%s>' % arg)
def sluggify(self):
""
self.request.env.update(
(k.lower().replace('.', '_'), v) for k, v in self.env.iteritems())
def update_request(self):
'''
update request from self
build env.request_uri
make lower-case versions of http headers in env
'''
"""
Updates request from self
Builds env.request_uri
Makes lower-case versions of http headers in env
"""
self.request.application = self.application
self.request.controller = self.controller
self.request.function = self.function
@@ -1105,27 +1104,27 @@ class MapUrlIn(object):
@property
def arg0(self):
"return first arg"
"Returns first arg"
return self.args(0)
@property
def harg0(self):
"return first arg with optional hyphen mapping"
"Returns first arg with optional hyphen mapping"
if self.map_hyphen and self.args(0):
return self.args(0).replace('-', '_')
return self.args(0)
def pop_arg_if(self, dopop):
"conditionally remove first arg and return new first arg"
"Conditionally removes first arg and returns new first arg"
if dopop:
self.args.pop(0)
class MapUrlOut(object):
"logic for mapping outgoing URLs"
"Logic for mapping outgoing URLs"
def __init__(self, request, env, application, controller,
function, args, other, scheme, host, port):
function, args, other, scheme, host, port, language):
"initialize a map-out object"
self.default_application = routers.BASE.default_application
if application in routers:
@@ -1144,6 +1143,7 @@ class MapUrlOut(object):
self.scheme = scheme
self.host = host
self.port = port
self.language = language
self.applications = routers.BASE.applications
self.controllers = self.router.controllers
@@ -1166,7 +1166,7 @@ class MapUrlOut(object):
if (self.router.exclusive_domain and self.domain_application and self.domain_application != self.application and not self.host):
raise SyntaxError('cross-domain conflict: must specify host')
lang = request and request.uri_language
lang = self.language if self.language else request and request.uri_language
if lang and self.languages and lang in self.languages:
self.language = lang
else:
@@ -1178,13 +1178,13 @@ class MapUrlOut(object):
self.omit_function = False
def omit_lang(self):
"omit language if possible"
"Omits language if possible"
if not self.language or self.language == self.default_language:
self.omit_language = True
def omit_acf(self):
"omit what we can of a/c/f"
"Omits what we can of a/c/f"
router = self.router
@@ -1253,7 +1253,7 @@ class MapUrlOut(object):
self.omit_function = False
def build_acf(self):
"build acf from components"
"Builds a/c/f from components"
acf = ''
if self.map_hyphen:
self.application = self.application.replace('_', '-')
@@ -1279,7 +1279,7 @@ class MapUrlOut(object):
return acf or '/'
def acf(self):
"convert components to /app/lang/controller/function"
"Converts components to /app/lang/controller/function"
if not routers:
return None # use regex filter
@@ -1289,7 +1289,7 @@ class MapUrlOut(object):
def map_url_in(request, env, app=False):
"route incoming URL"
"Routes incoming URL"
# initialize router-url object
#
@@ -1331,9 +1331,9 @@ def map_url_in(request, env, app=False):
def map_url_out(request, env, application, controller,
function, args, other, scheme, host, port):
'''
supply /a/c/f (or /a/lang/c/f) portion of outgoing url
function, args, other, scheme, host, port, language=None):
"""
Supply /a/c/f (or /a/lang/c/f) portion of outgoing url
The basic rule is that we can only make transformations
that map_url_in can reverse.
@@ -1341,14 +1341,14 @@ def map_url_out(request, env, application, controller,
Suppose that the incoming arguments are a,c,f,args,lang
and that the router defaults are da, dc, df, dl.
We can perform these transformations trivially if args=[] and lang=None or dl:
We can perform these transformations trivially if args=[] and lang=None or dl::
/da/dc/df => /
/a/dc/df => /a
/a/c/df => /a/c
/da/dc/df => /
/a/dc/df => /a
/a/c/df => /a/c
We would also like to be able to strip the default application or application/controller
from URLs with function/args present, thus:
from URLs with function/args present, thus::
/da/c/f/args => /c/f/args
/da/dc/f/args => /f/args
@@ -1356,14 +1356,14 @@ def map_url_out(request, env, application, controller,
We use [applications] and [controllers] and {functions} to suppress ambiguous omissions.
We assume that language names do not collide with a/c/f names.
'''
"""
map = MapUrlOut(request, env, application, controller,
function, args, other, scheme, host, port)
function, args, other, scheme, host, port, language)
return map.acf()
def get_effective_router(appname):
"return a private copy of the effective router for the specified application"
"Returns a private copy of the effective router for the specified application"
if not routers or appname not in routers:
return None
return Storage(routers[appname]) # return a copy
+6 -8
View File
@@ -1,15 +1,13 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
::
# from http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/496942
# Title: Cross-site scripting (XSS) defense
# Submitter: Josh Goldfoot (other recipes)
# Last Updated: 2006/08/05
# Version no: 1.0
| From http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/496942
| Submitter: Josh Goldfoot (other recipes)
| Last Updated: 2006/08/05
| Version: 1.0
Cross-site scripting (XSS) defense
-----------------------------------
"""
+425 -189
View File
File diff suppressed because it is too large Load Diff
+5 -3
View File
@@ -1,7 +1,9 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
This file is part of the web2py Web Framework
Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu>
License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
| This file is part of the web2py Web Framework
| Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu>
| License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
"""
import os
+23 -27
View File
@@ -2,11 +2,13 @@
# -*- coding: utf-8 -*-
"""
This file is part of the web2py Web Framework
Developed by Massimo Di Pierro <mdipierro@cs.depaul.edu>,
limodou <limodou@gmail.com> and srackham <srackham@gmail.com>.
License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
| This file is part of the web2py Web Framework
| Developed by Massimo Di Pierro <mdipierro@cs.depaul.edu>,
| limodou <limodou@gmail.com> and srackham <srackham@gmail.com>.
| License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
Web2py environment in the shell
--------------------------------
"""
import os
@@ -56,17 +58,13 @@ def exec_environment(
response=None,
session=None,
):
"""
.. function:: gluon.shell.exec_environment([pyfile=''[, request=Request()
[, response=Response[, session=Session()]]]])
"""Environment builder and module loader.
Environment builder and module loader.
Builds a web2py environment and optionally executes a Python file into
the environment.
Builds a web2py environment and optionally executes a Python
file into the environment.
A Storage dictionary containing the resulting environment is returned.
The working directory must be web2py root -- this is the web2py default.
A Storage dictionary containing the resulting environment is returned.
The working directory must be web2py root -- this is the web2py default.
"""
@@ -103,17 +101,15 @@ def env(
extra_request={},
):
"""
Return web2py execution environment for application (a), controller (c),
Returns web2py execution environment for application (a), controller (c),
function (f).
If import_models is True the exec all application models into the
environment.
extra_request allows you to pass along any extra
variables to the request object before your models
get executed. This was mainly done to support
web2py_utils.test_runner, however you can use it
with any wrapper scripts that need access to the
web2py environment.
extra_request allows you to pass along any extra variables to the request
object before your models get executed. This was mainly done to support
web2py_utils.test_runner, however you can use it with any wrapper scripts
that need access to the web2py environment.
"""
request = Request({})
@@ -197,8 +193,8 @@ def run(
Start interactive shell or run Python script (startfile) in web2py
controller environment. appname is formatted like:
a web2py application name
a/c exec the controller c into the application environment
- a : web2py application name
- a/c : exec the controller c into the application environment
"""
(a, c, f, args, vars) = parse_path_info(appname, av=True)
@@ -323,8 +319,8 @@ def run(
def parse_path_info(path_info, av=False):
"""
Parse path info formatted like a/c/f where c and f are optional
and a leading / accepted.
Parses path info formatted like a/c/f where c and f are optional
and a leading `/` is accepted.
Return tuple (a, c, f). If invalid path_info a is set to None.
If c or f are omitted they are set to None.
If av=True, parse args and vars
@@ -358,9 +354,9 @@ def test(testpath, import_models=True, verbose=False):
"""
Run doctests in web2py environment. testpath is formatted like:
a tests all controllers in application a
a/c tests controller c in application a
a/c/f test function f in controller c, application a
- a: tests all controllers in application a
- a/c: tests controller c in application a
- a/c/f test function f in controller c, application a
Where a, c and f are application, controller and function names
respectively. If the testpath is a file name the file is tested.
+11 -1
View File
@@ -1,5 +1,15 @@
# this file exists for backward compatibility
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
| This file is part of the web2py Web Framework
| Developed by Massimo Di Pierro <mdipierro@cs.depaul.edu>,
| limodou <limodou@gmail.com> and srackham <srackham@gmail.com>.
| License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
Just for backward compatibility
--------------------------------
"""
__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
+408 -337
View File
File diff suppressed because it is too large Load Diff
+101 -74
View File
@@ -2,9 +2,9 @@
# -*- coding: utf-8 -*-
"""
This file is part of the web2py Web Framework
Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu>
License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
| This file is part of the web2py Web Framework
| Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu>
| License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
Provides:
@@ -25,6 +25,8 @@ class Storage(dict):
A Storage object is like a dictionary except `obj.foo` can be used
in addition to `obj['foo']`, and setting obj.foo = None deletes item foo.
Example::
>>> o = Storage(a=1)
>>> print o.a
1
@@ -39,6 +41,7 @@ class Storage(dict):
>>> del o.a
>>> print o.a
None
"""
__slots__ = ()
__setattr__ = dict.__setitem__
@@ -52,23 +55,25 @@ class Storage(dict):
def getlist(self, key):
"""
Return a Storage value as a list.
Returns a Storage value as a list.
If the value is a list it will be returned as-is.
If object is None, an empty list will be returned.
Otherwise, [value] will be returned.
Otherwise, `[value]` will be returned.
Example output for a query string of `?x=abc&y=abc&y=def`::
>>> request = Storage()
>>> request.vars = Storage()
>>> request.vars.x = 'abc'
>>> request.vars.y = ['abc', 'def']
>>> request.vars.getlist('x')
['abc']
>>> request.vars.getlist('y')
['abc', 'def']
>>> request.vars.getlist('z')
[]
Example output for a query string of ?x=abc&y=abc&y=def
>>> request = Storage()
>>> request.vars = Storage()
>>> request.vars.x = 'abc'
>>> request.vars.y = ['abc', 'def']
>>> request.vars.getlist('x')
['abc']
>>> request.vars.getlist('y')
['abc', 'def']
>>> request.vars.getlist('z')
[]
"""
value = self.get(key, [])
if value is None or isinstance(value, (list, tuple)):
@@ -78,43 +83,48 @@ class Storage(dict):
def getfirst(self, key, default=None):
"""
Return the first or only value when given a request.vars-style key.
Returns the first value of a list or the value itself when given a
`request.vars` style key.
If the value is a list, its first item will be returned;
otherwise, the value will be returned as-is.
Example output for a query string of ?x=abc&y=abc&y=def
>>> request = Storage()
>>> request.vars = Storage()
>>> request.vars.x = 'abc'
>>> request.vars.y = ['abc', 'def']
>>> request.vars.getfirst('x')
'abc'
>>> request.vars.getfirst('y')
'abc'
>>> request.vars.getfirst('z')
Example output for a query string of `?x=abc&y=abc&y=def`::
>>> request = Storage()
>>> request.vars = Storage()
>>> request.vars.x = 'abc'
>>> request.vars.y = ['abc', 'def']
>>> request.vars.getfirst('x')
'abc'
>>> request.vars.getfirst('y')
'abc'
>>> request.vars.getfirst('z')
"""
values = self.getlist(key)
return values[0] if values else default
def getlast(self, key, default=None):
"""
Returns the last or only single value when
given a request.vars-style key.
Returns the last value of a list or value itself when given a
`request.vars` style key.
If the value is a list, the last item will be returned;
otherwise, the value will be returned as-is.
Simulated output with a query string of ?x=abc&y=abc&y=def
>>> request = Storage()
>>> request.vars = Storage()
>>> request.vars.x = 'abc'
>>> request.vars.y = ['abc', 'def']
>>> request.vars.getlast('x')
'abc'
>>> request.vars.getlast('y')
'def'
>>> request.vars.getlast('z')
Simulated output with a query string of `?x=abc&y=abc&y=def`::
>>> request = Storage()
>>> request.vars = Storage()
>>> request.vars.x = 'abc'
>>> request.vars.y = ['abc', 'def']
>>> request.vars.getlast('x')
'abc'
>>> request.vars.getlast('y')
'def'
>>> request.vars.getlast('z')
"""
values = self.getlist(key)
return values[-1] if values else default
@@ -124,7 +134,7 @@ PICKABLE = (str, int, long, float, bool, list, dict, tuple, set)
class StorageList(Storage):
"""
like Storage but missing elements default to [] instead of None
Behaves like Storage but missing elements defaults to [] instead of None
"""
def __getitem__(self, key):
return self.__getattr__(key)
@@ -183,35 +193,36 @@ class FastStorage(dict):
Eventually this should replace class Storage but causes memory leak
because of http://bugs.python.org/issue1469629
>>> s = FastStorage()
>>> s.a = 1
>>> s.a
1
>>> s['a']
1
>>> s.b
>>> s['b']
>>> s['b']=2
>>> s['b']
2
>>> s.b
2
>>> isinstance(s,dict)
True
>>> dict(s)
{'a': 1, 'b': 2}
>>> dict(FastStorage(s))
{'a': 1, 'b': 2}
>>> import pickle
>>> s = pickle.loads(pickle.dumps(s))
>>> dict(s)
{'a': 1, 'b': 2}
>>> del s.b
>>> del s.a
>>> s.a
>>> s.b
>>> s['a']
>>> s['b']
>>> s = FastStorage()
>>> s.a = 1
>>> s.a
1
>>> s['a']
1
>>> s.b
>>> s['b']
>>> s['b']=2
>>> s['b']
2
>>> s.b
2
>>> isinstance(s,dict)
True
>>> dict(s)
{'a': 1, 'b': 2}
>>> dict(FastStorage(s))
{'a': 1, 'b': 2}
>>> import pickle
>>> s = pickle.loads(pickle.dumps(s))
>>> dict(s)
{'a': 1, 'b': 2}
>>> del s.b
>>> del s.a
>>> s.a
>>> s.b
>>> s['a']
>>> s['b']
"""
def __init__(self, *args, **kwargs):
dict.__init__(self, *args, **kwargs)
@@ -246,14 +257,30 @@ class FastStorage(dict):
class List(list):
"""
Like a regular python list but a[i] if i is out of bounds return None
instead of IndexOutOfBounds
Like a regular python list but a[i] if i is out of bounds returns None
instead of `IndexOutOfBounds`
"""
def __call__(self, i, default=DEFAULT, cast=None, otherwise=None):
"""
request.args(0,default=0,cast=int,otherwise='http://error_url')
request.args(0,default=0,cast=int,otherwise=lambda:...)
"""Allows to use a special syntax for fast-check of `request.args()`
validity
Args:
i: index
default: use this value if arg not found
cast: type cast
otherwise: can be:
- None: results in a 404
- str: redirect to this address
- callable: calls the function (nothing is passed)
Example:
You can use::
request.args(0,default=0,cast=int,otherwise='http://error_url')
request.args(0,default=0,cast=int,otherwise=lambda:...)
"""
n = len(self)
if 0 <= i < n or -n <= i < 0:
+6 -3
View File
@@ -2,9 +2,12 @@
# -*- coding: utf-8 -*-
"""
This file is part of the web2py Web Framework
Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu>
License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
| This file is part of the web2py Web Framework
| Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu>
| License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
Facilities to handle file streaming
------------------------------------
"""
import os
+89 -71
View File
@@ -2,16 +2,16 @@
# -*- coding: utf-8 -*-
"""
This file is part of the web2py Web Framework (Copyrighted, 2007-2011).
License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
| This file is part of the web2py Web Framework
| License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
| Author: Thadeus Burgess
| Contributors:
| - Massimo Di Pierro for creating the original gluon/template.py
| - Jonathan Lundell for extensively testing the regex on Jython.
| - Limodou (creater of uliweb) who inspired the block-element support for web2py.
Author: Thadeus Burgess
Contributors:
- Thank you to Massimo Di Pierro for creating the original gluon/template.py
- Thank you to Jonathan Lundell for extensively testing the regex on Jython.
- Thank you to Limodou (creater of uliweb) who inspired the block-element support for web2py.
Templating syntax
------------------
"""
import os
@@ -90,6 +90,7 @@ class BlockNode(Node):
{{ block test }}
This is default block test
{{ end }}
"""
def __init__(self, name='', pre_extend=False, delimiters=('{{', '}}')):
"""
@@ -115,11 +116,10 @@ class BlockNode(Node):
def append(self, node):
"""
Add an element to the nodes.
Adds an element to the nodes.
Keyword Arguments
- node -- Node object or string to append.
Args:
node: Node object or string to append.
"""
if isinstance(node, str) or isinstance(node, Node):
self.nodes.append(node)
@@ -128,11 +128,10 @@ class BlockNode(Node):
def extend(self, other):
"""
Extend the list of nodes with another BlockNode class.
Extends the list of nodes with another BlockNode class.
Keyword Arguments
- other -- BlockNode or Content object to extend from.
Args:
other: BlockNode or Content object to extend from.
"""
if isinstance(other, BlockNode):
self.nodes.extend(other.nodes)
@@ -143,8 +142,9 @@ class BlockNode(Node):
def output(self, blocks):
"""
Merges all nodes into a single string.
blocks -- Dictionary of blocks that are extending
from this template.
Args:
blocks: Dictionary of blocks that are extending from this template.
"""
return ''.join(output_aux(node, blocks) for node in self.nodes)
@@ -154,13 +154,11 @@ class Content(BlockNode):
Parent Container -- Used as the root level BlockNode.
Contains functions that operate as such.
Args:
name: Unique name for this BlockNode
"""
def __init__(self, name="ContentBlock", pre_extend=False):
"""
Keyword Arguments
name -- Unique name for this BlockNode
"""
self.name = name
self.nodes = []
self.blocks = {}
@@ -220,6 +218,21 @@ class Content(BlockNode):
class TemplateParser(object):
"""Parse all blocks
Args:
text: text to parse
context: context to parse in
path: folder path to templates
writer: string of writer class to use
lexers: dict of custom lexers to use.
delimiters: for example `('{{','}}')`
_super_nodes: a list of nodes to check for inclusion
this should only be set by "self.extend"
It contains a list of SuperNodes from a child
template that need to be handled.
"""
default_delimiters = ('{{', '}}')
r_tag = compile(r'(\{\{.*?\}\})', DOTALL)
@@ -244,18 +257,6 @@ class TemplateParser(object):
delimiters=('{{', '}}'),
_super_nodes = [],
):
"""
text -- text to parse
context -- context to parse in
path -- folder path to templates
writer -- string of writer class to use
lexers -- dict of custom lexers to use.
delimiters -- for example ('{{','}}')
_super_nodes -- a list of nodes to check for inclusion
this should only be set by "self.extend"
It contains a list of SuperNodes from a child
template that need to be handled.
"""
# Keep a root level name.
self.name = name
@@ -317,18 +318,18 @@ class TemplateParser(object):
def to_string(self):
"""
Return the parsed template with correct indentation.
Returns the parsed template with correct indentation.
Used to make it easier to port to python3.
"""
return self.reindent(str(self.content))
def __str__(self):
"Make sure str works exactly the same as python 3"
"Makes sure str works exactly the same as python 3"
return self.to_string()
def __unicode__(self):
"Make sure str works exactly the same as python 3"
"Makes sure str works exactly the same as python 3"
return self.to_string()
def reindent(self, text):
@@ -411,13 +412,13 @@ class TemplateParser(object):
def _raise_error(self, message='', text=None):
"""
Raise an error using itself as the filename and textual content.
Raises an error using itself as the filename and textual content.
"""
raise RestrictedError(self.name, text or self.text, message)
def _get_file_text(self, filename):
"""
Attempt to open ``filename`` and retrieve its text.
Attempts to open ``filename`` and retrieve its text.
This will use self.path to search for the file.
"""
@@ -454,7 +455,7 @@ class TemplateParser(object):
def include(self, content, filename):
"""
Include ``filename`` here.
Includes ``filename`` here.
"""
text = self._get_file_text(filename)
@@ -469,8 +470,8 @@ class TemplateParser(object):
def extend(self, filename):
"""
Extend ``filename``. Anything not declared in a block defined by the
parent will be placed in the parent templates ``{{include}}`` block.
Extends `filename`. Anything not declared in a block defined by the
parent will be placed in the parent templates `{{include}}` block.
"""
# If no filename, create a dummy layout with only an {{include}}.
text = self._get_file_text(filename) or '%sinclude%s' % tuple(self.delimiters)
@@ -770,9 +771,12 @@ def parse_template(filename,
delimiters=('{{', '}}')
):
"""
filename can be a view filename in the views folder or an input stream
path is the path of a views folder
context is a dictionary of symbols used to render the template
Args:
filename: can be a view filename in the views folder or an input stream
path: is the path of a views folder
context: is a dictionary of symbols used to render the template
lexers: dict of custom lexers to use
delimiters: opening and closing tags
"""
# First, if we have a str try to open the file
@@ -841,30 +845,44 @@ def render(content="hello world",
writer='response.write'
):
"""
>>> render()
'hello world'
>>> render(content='abc')
'abc'
>>> render(content='abc\'')
"abc'"
>>> render(content='a"\'bc')
'a"\\'bc'
>>> render(content='a\\nbc')
'a\\nbc'
>>> render(content='a"bcd"e')
'a"bcd"e'
>>> render(content="'''a\\nc'''")
"'''a\\nc'''"
>>> render(content="'''a\\'c'''")
"'''a\'c'''"
>>> render(content='{{for i in range(a):}}{{=i}}<br />{{pass}}', context=dict(a=5))
'0<br />1<br />2<br />3<br />4<br />'
>>> render(content='{%for i in range(a):%}{%=i%}<br />{%pass%}', context=dict(a=5),delimiters=('{%','%}'))
'0<br />1<br />2<br />3<br />4<br />'
>>> render(content="{{='''hello\\nworld'''}}")
'hello\\nworld'
>>> render(content='{{for i in range(3):\\n=i\\npass}}')
'012'
Generic render function
Args:
content: default content
stream: file-like obj to read template from
filename: where to find template
path: base path for templates
context: env
lexers: custom lexers to use
delimiters: opening and closing tags
writer: where to inject the resulting stream
Example::
>>> render()
'hello world'
>>> render(content='abc')
'abc'
>>> render(content="abc'")
"abc'"
>>> render(content=''''a"'bc''')
'a"'bc'
>>> render(content='a\\nbc')
'a\\nbc'
>>> render(content='a"bcd"e')
'a"bcd"e'
>>> render(content="'''a\\nc'''")
"'''a\\nc'''"
>>> render(content="'''a\\'c'''")
"'''a\'c'''"
>>> render(content='{{for i in range(a):}}{{=i}}<br />{{pass}}', context=dict(a=5))
'0<br />1<br />2<br />3<br />4<br />'
>>> render(content='{%for i in range(a):%}{%=i%}<br />{%pass%}', context=dict(a=5),delimiters=('{%','%}'))
'0<br />1<br />2<br />3<br />4<br />'
>>> render(content="{{='''hello\\nworld'''}}")
'hello\\nworld'
>>> render(content='{{for i in range(3):\\n=i\\npass}}')
'012'
"""
# here to avoid circular Imports
try:
+11 -2
View File
@@ -1,6 +1,15 @@
import os, sys
from test_http import *
from test_cache import *
from test_dal import *
NOSQL = any([name in (os.getenv("DB") or "")
for name in ("datastore", "mongodb", "imap")])
if NOSQL:
from test_dal_nosql import *
else:
from test_dal import *
from test_html import *
from test_is_url import *
from test_languages import *
@@ -13,6 +22,6 @@ from test_utils import *
from test_contribs import *
from test_web import *
import sys
if sys.version[:3] == '2.7':
from test_old_doctests import *
+1
View File
@@ -25,6 +25,7 @@ exclude_lines =
ignore_errors = True
omit = gluon/contrib/*
gluon/tests/*
[html]
directory = coverage_html_report
+115 -84
View File
@@ -43,12 +43,12 @@ def fix_sys_path():
fix_sys_path()
from dal import DAL, Field, Table, SQLALL
#for travis-ci
DEFAULT_URI = os.environ.get('DB', 'sqlite:memory')
print 'Testing against %s engine (%s)' % (DEFAULT_URI.partition(':')[0], DEFAULT_URI)
from dal import DAL, Field, Table, SQLALL
ALLOWED_DATATYPES = [
'string',
@@ -79,9 +79,6 @@ def tearDownModule():
class TestFields(unittest.TestCase):
def testFieldName(self):
return
# Any table name is supported as long as underlying db does. The following code is ignored.
# Check that Fields cannot start with underscores
self.assertRaises(SyntaxError, Field, '_abc', 'string')
@@ -201,6 +198,25 @@ class TestFields(unittest.TestCase):
db.tt.drop()
class TestTables(unittest.TestCase):
def testTableNames(self):
# Check that Tables cannot start with underscores
self.assertRaises(SyntaxError, Table, None, '_abc')
# Check that Tables cannot contain punctuation other than underscores
self.assertRaises(SyntaxError, Table, None, 'a.bc')
# Check that Tables cannot be a name of a method or property of DAL
for x in ['define_table', 'tables', 'as_dict']:
self.assertRaises(SyntaxError, Table, None, x)
# Check that Table allows underscores in the body of a field name.
self.assert_(Table(None, 'a_bc'),
"Table isn't allowing underscores in tablename. It should.")
class TestAll(unittest.TestCase):
def setUp(self):
@@ -365,14 +381,35 @@ class TestLike(unittest.TestCase):
self.assertEqual(db(db.tt.aa.like('%b%')).count(), 1)
self.assertEqual(db(db.tt.aa.like('%c')).count(), 1)
self.assertEqual(db(db.tt.aa.like('%d%')).count(), 0)
self.assertEqual(db(db.tt.aa.lower().like('A%')).count(), 1)
self.assertEqual(db(db.tt.aa.lower().like('%B%')).count(),
1)
self.assertEqual(db(db.tt.aa.lower().like('%C')).count(), 1)
self.assertEqual(db(db.tt.aa.upper().like('A%')).count(), 1)
self.assertEqual(db(db.tt.aa.upper().like('%B%')).count(),
1)
self.assertEqual(db(db.tt.aa.upper().like('%C')).count(), 1)
#DAL maps like() (and contains(), startswith(), endswith())
#to the LIKE operator, that in ANSI-SQL is case-sensitive
#There are backends supporting case-sensitivity by default
#and backends that needs additional care to turn
#case-sensitivity on. To discern among those, let's run
#this query comparing previously inserted 'abc' with 'ABC':
#if the result is 0, then the backend recognizes
#case-sensitivity, if 1 it isn't
is_case_insensitive = db(db.tt.aa.like('%ABC%')).count()
if is_case_insensitive:
self.assertEqual(db(db.tt.aa.like('A%')).count(), 1)
self.assertEqual(db(db.tt.aa.like('%B%')).count(), 1)
self.assertEqual(db(db.tt.aa.like('%C')).count(), 1)
self.assertEqual(db(db.tt.aa.like('A%', case_sensitive=False)).count(), 1)
self.assertEqual(db(db.tt.aa.like('%B%', case_sensitive=False)).count(), 1)
self.assertEqual(db(db.tt.aa.like('%C', case_sensitive=False)).count(), 1)
self.assertEqual(db(db.tt.aa.upper().like('A%')).count(), 1)
self.assertEqual(db(db.tt.aa.upper().like('%B%')).count(),1)
self.assertEqual(db(db.tt.aa.upper().like('%C')).count(), 1)
else:
self.assertEqual(db(db.tt.aa.like('A%')).count(), 0)
self.assertEqual(db(db.tt.aa.like('%B%')).count(), 0)
self.assertEqual(db(db.tt.aa.like('%C')).count(), 0)
self.assertEqual(db(db.tt.aa.like('A%', case_sensitive=False)).count(), 1)
self.assertEqual(db(db.tt.aa.like('%B%', case_sensitive=False)).count(), 1)
self.assertEqual(db(db.tt.aa.like('%C', case_sensitive=False)).count(), 1)
self.assertEqual(db(db.tt.aa.upper().like('A%')).count(), 1)
self.assertEqual(db(db.tt.aa.upper().like('%B%')).count(),1)
self.assertEqual(db(db.tt.aa.upper().like('%C')).count(), 1)
db.tt.drop()
db.define_table('tt', Field('aa', 'integer'))
self.assertEqual(db.tt.insert(aa=1111111111), 1)
@@ -866,7 +903,7 @@ class TestRNameTable(unittest.TestCase):
def testSelect(self):
db = DAL(DEFAULT_URI, check_reserved=['all'])
rname = db._adapter.QUOTE_TEMPLATE % 'a very complicated tablename'
rname = db._adapter.__class__.QUOTE_TEMPLATE % 'a very complicated tablename'
db.define_table(
'easy_name',
Field('a_field'),
@@ -895,14 +932,14 @@ class TestRNameTable(unittest.TestCase):
avg = db.easy_name.id.avg()
rtn = db(db.easy_name.id > 0).select(avg)
self.assertEqual(rtn[0][avg], 3)
rname = db._adapter.QUOTE_TEMPLATE % 'this is the person table'
rname = db._adapter.__class__.QUOTE_TEMPLATE % 'this is the person table'
db.define_table(
'person',
Field('name', default="Michael"),
Field('uuid'),
rname=rname
)
rname = db._adapter.QUOTE_TEMPLATE % 'this is the pet table'
rname = db._adapter.__class__.QUOTE_TEMPLATE % 'this is the pet table'
db.define_table(
'pet',
Field('friend','reference person'),
@@ -975,7 +1012,7 @@ class TestRNameTable(unittest.TestCase):
for key in ['reference','reference FK']:
db._adapter.types[key]=db._adapter.types[key].replace(
'%(on_delete_action)s','NO ACTION')
rname = db._adapter.QUOTE_TEMPLATE % 'the cubs'
rname = db._adapter.__class__.QUOTE_TEMPLATE % 'the cubs'
db.define_table('pet_farm',
Field('name'),
Field('father','reference pet_farm'),
@@ -1018,8 +1055,8 @@ class TestRNameTable(unittest.TestCase):
def testJoin(self):
db = DAL(DEFAULT_URI, check_reserved=['all'])
rname = db._adapter.QUOTE_TEMPLATE % 'this is table t1'
rname2 = db._adapter.QUOTE_TEMPLATE % 'this is table t2'
rname = db._adapter.__class__.QUOTE_TEMPLATE % 'this is table t1'
rname2 = db._adapter.__class__.QUOTE_TEMPLATE % 'this is table t2'
db.define_table('t1', Field('aa'), rname=rname)
db.define_table('t2', Field('aa'), Field('b', db.t1), rname=rname2)
i1 = db.t1.insert(aa='1')
@@ -1089,8 +1126,8 @@ class TestRNameFields(unittest.TestCase):
# tests for highly experimental rname attribute
def testSelect(self):
db = DAL(DEFAULT_URI, check_reserved=['all'])
rname = db._adapter.QUOTE_TEMPLATE % 'a very complicated fieldname'
rname2 = db._adapter.QUOTE_TEMPLATE % 'rrating from 1 to 10'
rname = db._adapter.__class__.QUOTE_TEMPLATE % 'a very complicated fieldname'
rname2 = db._adapter.__class__.QUOTE_TEMPLATE % 'rrating from 1 to 10'
db.define_table(
'easy_name',
Field('a_field', rname=rname),
@@ -1124,13 +1161,13 @@ class TestRNameFields(unittest.TestCase):
rtn = db(db.easy_name.id > 0).select(avg)
self.assertEqual(rtn[0][avg], 2)
rname = db._adapter.QUOTE_TEMPLATE % 'this is the person name'
rname = db._adapter.__class__.QUOTE_TEMPLATE % 'this is the person name'
db.define_table(
'person',
Field('name', default="Michael", rname=rname),
Field('uuid')
)
rname = db._adapter.QUOTE_TEMPLATE % 'this is the pet name'
rname = db._adapter.__class__.QUOTE_TEMPLATE % 'this is the pet name'
db.define_table(
'pet',
Field('friend','reference person'),
@@ -1197,7 +1234,7 @@ class TestRNameFields(unittest.TestCase):
self.assertEqual(rtn[2].pet.name, 'Gertie')
#aliases
rname = db._adapter.QUOTE_TEMPLATE % 'the cub name'
rname = db._adapter.__class__.QUOTE_TEMPLATE % 'the cub name'
if DEFAULT_URI.startswith('mssql'):
#multiple cascade gotcha
for key in ['reference','reference FK']:
@@ -1244,7 +1281,7 @@ class TestRNameFields(unittest.TestCase):
def testRun(self):
db = DAL(DEFAULT_URI, check_reserved=['all'])
rname = db._adapter.QUOTE_TEMPLATE % 'a very complicated fieldname'
rname = db._adapter.__class__.QUOTE_TEMPLATE % 'a very complicated fieldname'
for ft in ['string', 'text', 'password', 'upload', 'blob']:
db.define_table('tt', Field('aa', ft, default='', rname=rname))
self.assertEqual(db.tt.insert(aa='x'), 1)
@@ -1314,7 +1351,7 @@ class TestRNameFields(unittest.TestCase):
def testInsert(self):
db = DAL(DEFAULT_URI, check_reserved=['all'])
rname = db._adapter.QUOTE_TEMPLATE % 'a very complicated fieldname'
rname = db._adapter.__class__.QUOTE_TEMPLATE % 'a very complicated fieldname'
db.define_table('tt', Field('aa', rname=rname))
self.assertEqual(db.tt.insert(aa='1'), 1)
self.assertEqual(db.tt.insert(aa='1'), 2)
@@ -1330,8 +1367,8 @@ class TestRNameFields(unittest.TestCase):
def testJoin(self):
db = DAL(DEFAULT_URI, check_reserved=['all'])
rname = db._adapter.QUOTE_TEMPLATE % 'this is field aa'
rname2 = db._adapter.QUOTE_TEMPLATE % 'this is field b'
rname = db._adapter.__class__.QUOTE_TEMPLATE % 'this is field aa'
rname2 = db._adapter.__class__.QUOTE_TEMPLATE % 'this is field b'
db.define_table('t1', Field('aa', rname=rname))
db.define_table('t2', Field('aa', rname=rname), Field('b', db.t1, rname=rname2))
i1 = db.t1.insert(aa='1')
@@ -1397,65 +1434,29 @@ class TestRNameFields(unittest.TestCase):
db.person.drop()
class TestQuoting(unittest.TestCase):
# tests for complex table names
def testRun(self):
db = DAL(DEFAULT_URI, check_reserved=['all'])
t0 = db.define_table('A.table.with.dots and spaces',
Field('f', 'string'))
t1 = db.define_table('A.table',
Field('f.other', t0),
Field('words', 'text'))
blather = 'blah blah and so'
t0[0] = {'f': 'content'}
t1[0] = {'f.other': int(t0[1]['id']),
'words': blather}
r = db(t1['f.other']==t0.id).select()
self.assertEqual(r[0][db['A.table']].words, blather)
db.define_table('t0', Field('f0'))
db.define_table('t1', Field('f1'), Field('t0', db['t0']))
db.t0[0]=dict(f0=3)
db.t1[0]=dict(f1=3, t0=1)
rows=db(db.t0.id==db.t1.t0).select()
self.assertEqual(rows[0].t1.t0, rows[0].t0.id)
t0.drop('cascade')
t1.drop()
db.t1.drop()
db.t0.drop()
# tests for case sensitivity
def testCase(self):
db = DAL(DEFAULT_URI, check_reserved=['all'], ignore_field_case=False)
db = DAL(DEFAULT_URI, check_reserved=['all'], ignore_field_case=False, entity_quoting=True)
if DEFAULT_URI.startswith('mssql'):
#multiple cascade gotcha
for key in ['reference','reference FK']:
db._adapter.types[key]=db._adapter.types[key].replace(
'%(on_delete_action)s','NO ACTION')
# test table case
t0 = db.define_table('B',
t0 = db.define_table('t0',
Field('f', 'string'))
try:
t1 = db.define_table('b',
Field('B', t0),
Field('words', 'text'))
except Exception, e:
# An error is expected when database does not support case
# sensitive entity names.
if DEFAULT_URI.startswith('sqlite:'):
self.assertTrue(isinstance(e, db._adapter.driver.OperationalError))
return
raise e
t1 = db.define_table('b',
Field('B', t0),
Field('words', 'text'))
blather = 'blah blah and so'
t0[0] = {'f': 'content'}
t1[0] = {'B': int(t0[1]['id']),
'words': blather}
r = db(db.B.id==db.b.B).select()
r = db(db.t0.id==db.b.B).select()
self.assertEqual(r[0].b.words, blather)
@@ -1464,12 +1465,12 @@ class TestQuoting(unittest.TestCase):
# test field case
try:
t0 = db.define_table('table is a test',
t0 = db.define_table('table_is_a_test',
Field('a_a'),
Field('a_A'))
except Exception, e:
# some db does not support case sensitive field names mysql is one of them.
if DEFAULT_URI.startswith('mysql:'):
if DEFAULT_URI.startswith('mysql:') or DEFAULT_URI.startswith('sqlite:'):
db.rollback()
return
raise e
@@ -1482,16 +1483,21 @@ class TestQuoting(unittest.TestCase):
t0.drop()
def testPKFK(self):
# test primary keys
db = DAL(DEFAULT_URI, check_reserved=['all'], ignore_field_case=False)
if DEFAULT_URI.startswith('mssql'):
#multiple cascade gotcha
for key in ['reference','reference FK']:
db._adapter.types[key]=db._adapter.types[key].replace(
'%(on_delete_action)s','NO ACTION')
# test table without surrogate key. Length must is limited to
# 100 because of MySQL limitations: it cannot handle more than
# 767 bytes in unique keys.
t0 = db.define_table('t0', Field('Code', length=100), primarykey=['Code'])
t22 = db.define_table('t22', Field('f'), Field('t0_Code', 'reference t0'))
t2 = db.define_table('t2', Field('f'), Field('t0_Code', 'reference t0'))
t3 = db.define_table('t3', Field('f', length=100), Field('t0_Code', t0.Code), primarykey=['f'])
t4 = db.define_table('t4', Field('f', length=100), Field('t0', t0), primarykey=['f'])
@@ -1500,11 +1506,36 @@ class TestQuoting(unittest.TestCase):
except Exception, e:
self.assertTrue(isinstance(e, KeyError))
if DEFAULT_URI.startswith('mssql'):
#there's no drop cascade in mssql
t3.drop()
t4.drop()
t2.drop()
t0.drop()
else:
t0.drop('cascade')
t2.drop()
t3.drop()
t4.drop()
class TestTableAndFieldCase(unittest.TestCase):
"""
at the Python level we should not allow db.C and db.c because of .table conflicts on windows
but it should be possible to map two different names into distinct tables "c" and "C" at the Python level
By default Python models names should be mapped into lower case table names and assume case insensitivity.
"""
def testme(self):
return
class TestQuotesByDefault(unittest.TestCase):
"""
all default tables names should be quoted unless an explicit mapping has been given for a table.
"""
def testme(self):
return
t0.drop('cascade')
t22.drop()
t3.drop()
t4.drop()
if __name__ == '__main__':
unittest.main()
tearDownModule()
File diff suppressed because it is too large Load Diff
+85 -3
View File
@@ -38,6 +38,7 @@ def fix_sys_path():
fix_sys_path()
from html import *
from storage import Storage
class TestBareHelpers(unittest.TestCase):
@@ -69,8 +70,50 @@ class TestBareHelpers(unittest.TestCase):
'<meta a="1" b="2" />')
def testA(self):
self.assertEqual(A('<>', _a='1', _b='2').xml(),
'<a a="1" b="2" data-w2p_disable_with="default">&lt;&gt;</a>')
self.assertEqual(
A('<>', _a='1', _b='2').xml(),
'<a a="1" b="2">&lt;&gt;</a>'
)
self.assertEqual(
A('a', cid='b').xml(),
'<a data-w2p_disable_with="default" data-w2p_method="GET" data-w2p_target="b">a</a>'
)
self.assertEqual(
A('a', callback='b', _id='c').xml(),
'<a data-w2p_disable_with="default" data-w2p_method="POST" href="b" id="c">a</a>'
)
self.assertEqual(
A('a', delete='tr').xml(),
'<a data-w2p_disable_with="default" data-w2p_remove="tr">a</a>'
)
self.assertEqual(
A('a', _id='b', target='<self>').xml(),
'<a data-w2p_disable_with="default" data-w2p_target="b" id="b">a</a>'
)
self.assertEqual(
A('a', component='b').xml(),
'<a data-w2p_disable_with="default" data-w2p_method="GET" href="b">a</a>'
)
self.assertEqual(
A('a', _id='b', callback='c', noconfirm=True).xml(),
'<a data-w2p_disable_with="default" data-w2p_method="POST" href="c" id="b">a</a>'
)
self.assertEqual(
A('a', cid='b').xml(),
'<a data-w2p_disable_with="default" data-w2p_method="GET" data-w2p_target="b">a</a>'
)
self.assertEqual(
A('a', cid='b', _disable_with='processing...').xml(),
'<a data-w2p_disable_with="processing..." data-w2p_method="GET" data-w2p_target="b">a</a>'
)
self.assertEqual(
A('a', callback='b', delete='tr', noconfirm=True, _id='c').xml(),
'<a data-w2p_disable_with="default" data-w2p_method="POST" data-w2p_remove="tr" href="b" id="c">a</a>'
)
self.assertEqual(
A('a', callback='b', delete='tr', confirm='Are you sure?', _id='c').xml(),
'<a data-w2p_confirm="Are you sure?" data-w2p_disable_with="default" data-w2p_method="POST" data-w2p_remove="tr" href="b" id="c">a</a>'
)
def testB(self):
self.assertEqual(B('<>', _a='1', _b='2').xml(),
@@ -234,10 +277,49 @@ class TestBareHelpers(unittest.TestCase):
self.assertEqual(UL('<>', _a='1', _b='2').xml(),
'<ul a="1" b="2"><li>&lt;&gt;</li></ul>')
def testStaticURL(self):
# test response.static_version coupled with response.static_version_urls
self.assertEqual(URL('a', 'c', 'f'), '/a/c/f')
self.assertEqual(URL('a', 'static', 'design.css'), '/a/static/design.css')
response = Storage()
response.static_version = '1.2.3'
from globals import current
current.response = response
self.assertEqual(URL('a', 'static', 'design.css'), '/a/static/design.css')
response.static_version_urls = True
self.assertEqual(URL('a', 'static', 'design.css'), '/a/static/_1.2.3/design.css')
def testURL(self):
self.assertEqual(URL('a', 'c', 'f', args='1'), '/a/c/f/1')
self.assertEqual(URL('a', 'c', 'f', args=('1', '2')), '/a/c/f/1/2')
self.assertEqual(URL('a', 'c', 'f', args=['1', '2']), '/a/c/f/1/2')
self.assertEqual(URL('a', 'c', '/f'), '/a/c/f')
self.assertEqual(URL('a', 'c', 'f.json'), '/a/c/f.json')
self.assertRaises(SyntaxError, URL, *['a'])
request = Storage()
request.application = 'a'
request.controller = 'c'
request.function = 'f'
request.env = {}
from globals import current
current.request = request
must_return = '/a/c/f'
self.assertEqual(URL('f'), must_return)
self.assertEqual(URL('c', 'f'), must_return)
self.assertEqual(URL('a', 'c', 'f'), must_return)
self.assertEqual(URL('a', 'c', 'f', extension='json'), '/a/c/f.json')
def weird():
pass
self.assertEqual(URL('a', 'c', weird), '/a/c/weird')
self.assertRaises(SyntaxError, URL, *['a','c', 1])
class TestData(unittest.TestCase):
def testAdata(self):
self.assertEqual(A('<>', data=dict(abc='<def?asd>', cde='standard'), _a='1', _b='2').xml(),'<a a="1" b="2" data-abc="&lt;def?asd&gt;" data-cde="standard" data-w2p_disable_with="default">&lt;&gt;</a>')
self.assertEqual(A('<>', data=dict(abc='<def?asd>', cde='standard'), _a='1', _b='2').xml(),'<a a="1" b="2" data-abc="&lt;def?asd&gt;" data-cde="standard">&lt;&gt;</a>')
if __name__ == '__main__':
+41 -41
View File
@@ -55,10 +55,10 @@ class TestIsUrl(unittest.TestCase):
self.assertEqual(x('google.ca:80'), ('http://google.ca:80',
None))
self.assertEqual(x('unreal.blargg'), ('unreal.blargg',
'enter a valid URL'))
self.assertEqual(x('google..ca'), ('google..ca', 'enter a valid URL'))
'Enter a valid URL'))
self.assertEqual(x('google..ca'), ('google..ca', 'Enter a valid URL'))
self.assertEqual(
x('google.ca..'), ('google.ca..', 'enter a valid URL'))
x('google.ca..'), ('google.ca..', 'Enter a valid URL'))
# explicit use of 'http' mode
@@ -69,7 +69,7 @@ class TestIsUrl(unittest.TestCase):
self.assertEqual(x('google.ca:80'), ('http://google.ca:80',
None))
self.assertEqual(x('unreal.blargg'), ('unreal.blargg',
'enter a valid URL'))
'Enter a valid URL'))
# prepends 'https' instead of 'http'
@@ -80,7 +80,7 @@ class TestIsUrl(unittest.TestCase):
self.assertEqual(x('google.ca:80'), ('https://google.ca:80',
None))
self.assertEqual(x('unreal.blargg'), ('unreal.blargg',
'enter a valid URL'))
'Enter a valid URL'))
# prepending disabled
@@ -90,7 +90,7 @@ class TestIsUrl(unittest.TestCase):
self.assertEqual(x('google.ca'), ('google.ca', None))
self.assertEqual(x('google.ca:80'), ('google.ca:80', None))
self.assertEqual(x('unreal.blargg'), ('unreal.blargg',
'enter a valid URL'))
'Enter a valid URL'))
# custom allowed_schemes
@@ -98,12 +98,12 @@ class TestIsUrl(unittest.TestCase):
self.assertEqual(x('http://google.ca'), ('http://google.ca',
None))
self.assertEqual(x('https://google.ca'), ('https://google.ca',
'enter a valid URL'))
'Enter a valid URL'))
self.assertEqual(x('google.ca'), ('http://google.ca', None))
self.assertEqual(x('google.ca:80'), ('http://google.ca:80',
None))
self.assertEqual(x('unreal.blargg'), ('unreal.blargg',
'enter a valid URL'))
'Enter a valid URL'))
# custom allowed_schemes, excluding None
@@ -111,32 +111,32 @@ class TestIsUrl(unittest.TestCase):
self.assertEqual(x('http://google.ca'), ('http://google.ca',
None))
self.assertEqual(x('https://google.ca'), ('https://google.ca',
'enter a valid URL'))
self.assertEqual(x('google.ca'), ('google.ca', 'enter a valid URL'))
'Enter a valid URL'))
self.assertEqual(x('google.ca'), ('google.ca', 'Enter a valid URL'))
self.assertEqual(x('google.ca:80'), ('google.ca:80',
'enter a valid URL'))
'Enter a valid URL'))
self.assertEqual(x('unreal.blargg'), ('unreal.blargg',
'enter a valid URL'))
'Enter a valid URL'))
# custom allowed_schemes and prepend_scheme
x = IS_URL(allowed_schemes=[None, 'https'],
prepend_scheme='https')
self.assertEqual(x('http://google.ca'), ('http://google.ca',
'enter a valid URL'))
'Enter a valid URL'))
self.assertEqual(x('https://google.ca'), ('https://google.ca',
None))
self.assertEqual(x('google.ca'), ('https://google.ca', None))
self.assertEqual(x('google.ca:80'), ('https://google.ca:80',
None))
self.assertEqual(x('unreal.blargg'), ('unreal.blargg',
'enter a valid URL'))
'Enter a valid URL'))
# Now any URL requiring prepending will fail, but prepending is still
# enabled!
x = IS_URL(allowed_schemes=['http'])
self.assertEqual(x('google.ca'), ('google.ca', 'enter a valid URL'))
self.assertEqual(x('google.ca'), ('google.ca', 'Enter a valid URL'))
def testModeGeneric(self):
@@ -147,7 +147,7 @@ class TestIsUrl(unittest.TestCase):
self.assertEqual(x('google.ca'), ('google.ca', None))
self.assertEqual(x('google.ca:80'), ('http://google.ca:80', None))
self.assertEqual(x('blargg://unreal'), ('blargg://unreal',
'enter a valid URL'))
'Enter a valid URL'))
# 'generic' mode with custom allowed_schemes that still includes
# 'http' (the default for prepend_scheme)
@@ -156,10 +156,10 @@ class TestIsUrl(unittest.TestCase):
self.assertEqual(x('http://google.ca'), ('http://google.ca',
None))
self.assertEqual(x('ftp://google.ca'), ('ftp://google.ca',
'enter a valid URL'))
self.assertEqual(x('google.ca'), ('google.ca', 'enter a valid URL'))
'Enter a valid URL'))
self.assertEqual(x('google.ca'), ('google.ca', 'Enter a valid URL'))
self.assertEqual(x('google.ca:80'), ('google.ca:80',
'enter a valid URL'))
'Enter a valid URL'))
self.assertEqual(x('blargg://unreal'), ('blargg://unreal',
None))
@@ -174,27 +174,27 @@ class TestIsUrl(unittest.TestCase):
self.assertEqual(x('google.ca:80'), ('ftp://google.ca:80',
None))
self.assertEqual(x('blargg://unreal'), ('blargg://unreal',
'enter a valid URL'))
'Enter a valid URL'))
# 'generic' mode with overriden allowed_schemes and prepend_scheme
x = IS_URL(mode='generic', allowed_schemes=[None, 'ftp', 'ftps'
], prepend_scheme='ftp')
self.assertEqual(x('http://google.ca'), ('http://google.ca',
'enter a valid URL'))
'Enter a valid URL'))
self.assertEqual(x('google.ca'), ('google.ca', None))
self.assertEqual(x('ftp://google.ca'), ('ftp://google.ca',
None))
self.assertEqual(x('google.ca:80'), ('ftp://google.ca:80',
None))
self.assertEqual(x('blargg://unreal'), ('blargg://unreal',
'enter a valid URL'))
'Enter a valid URL'))
# Now any URL requiring prepending will fail, but prepending is still
# enabled!
x = IS_URL(mode='generic', allowed_schemes=['http'])
self.assertEqual(x('google.ca'), ('google.ca', 'enter a valid URL'))
self.assertEqual(x('google.ca'), ('google.ca', 'Enter a valid URL'))
def testExceptionalUse(self):
@@ -305,14 +305,14 @@ class TestIsUrl(unittest.TestCase):
# prepend_scheme has the invalid value 'http', we don't care!
x = IS_URL(allowed_schemes=['https'], prepend_scheme='https')
self.assertEqual(x('google.ca'), ('google.ca', 'enter a valid URL'))
self.assertEqual(x('google.ca'), ('google.ca', 'Enter a valid URL'))
# Not inluding None in the allowed_schemes essentially disabled prepending, so even though
# prepend_scheme has the invalid value 'http', we don't care!
x = IS_URL(mode='generic', allowed_schemes=['https'],
prepend_scheme='https')
self.assertEqual(x('google.ca'), ('google.ca', 'enter a valid URL'))
self.assertEqual(x('google.ca'), ('google.ca', 'Enter a valid URL'))
# ##############################################################################
@@ -434,7 +434,7 @@ class TestIsGenericUrl(unittest.TestCase):
y = IS_GENERIC_URL(allowed_schemes=['http', 'blargg'],
prepend_scheme='http')
self.assertEqual(y('google.ca'), ('google.ca', 'enter a valid URL'))
self.assertEqual(y('google.ca'), ('google.ca', 'Enter a valid URL'))
# ##############################################################################
@@ -605,9 +605,9 @@ class TestIsHttpUrl(unittest.TestCase):
# Does not prepend if None type is not specified in allowed_scheme, because a scheme is required
a = IS_HTTP_URL(allowed_schemes=['http'])
self.assertEqual(a('google.ca'), ('google.ca', 'enter a valid URL'))
self.assertEqual(a('google.ca'), ('google.ca', 'Enter a valid URL'))
self.assertEqual(a('google.ca:80'), ('google.ca:80',
'enter a valid URL'))
'Enter a valid URL'))
class TestUnicode(unittest.TestCase):
@@ -667,33 +667,33 @@ class TestUnicode(unittest.TestCase):
def testInvalidUrls(self):
self.assertEquals(
self.x(u'://ABC.com'), (u'://ABC.com', 'enter a valid URL'))
self.x(u'://ABC.com'), (u'://ABC.com', 'Enter a valid URL'))
self.assertEquals(self.x(u'http://\u4e2d\u4fd4.dne'), (
u'http://\u4e2d\u4fd4.dne', 'enter a valid URL'))
u'http://\u4e2d\u4fd4.dne', 'Enter a valid URL'))
self.assertEquals(self.x(u'https://google.dne'), (
u'https://google.dne', 'enter a valid URL'))
u'https://google.dne', 'Enter a valid URL'))
self.assertEquals(self.x(u'https://google..ca'), (
u'https://google..ca', 'enter a valid URL'))
u'https://google..ca', 'Enter a valid URL'))
self.assertEquals(
self.x(u'google..ca'), (u'google..ca', 'enter a valid URL'))
self.x(u'google..ca'), (u'google..ca', 'Enter a valid URL'))
self.assertEquals(self.x(u'http://' + u'\u4e2d' * 1000 + u'.com'), (
u'http://' + u'\u4e2d' * 1000 + u'.com', 'enter a valid URL'))
u'http://' + u'\u4e2d' * 1000 + u'.com', 'Enter a valid URL'))
self.assertEquals(self.x(u'http://google.com#fragment_\u4e86'), (
u'http://google.com#fragment_\u4e86', 'enter a valid URL'))
u'http://google.com#fragment_\u4e86', 'Enter a valid URL'))
self.assertEquals(self.x(u'http\u4e86://google.com'), (
u'http\u4e86://google.com', 'enter a valid URL'))
u'http\u4e86://google.com', 'Enter a valid URL'))
self.assertEquals(self.x(u'http\u4e86://google.com#fragment_\u4e86'), (
u'http\u4e86://google.com#fragment_\u4e86', 'enter a valid URL'))
u'http\u4e86://google.com#fragment_\u4e86', 'Enter a valid URL'))
self.assertEquals(self.y(u'http://\u4e2d\u4fd4.com/\u4e86'), (
u'http://\u4e2d\u4fd4.com/\u4e86', 'enter a valid URL'))
#self.assertEquals(self.y(u'google.ca'), (u'google.ca', 'enter a valid URL'))
u'http://\u4e2d\u4fd4.com/\u4e86', 'Enter a valid URL'))
#self.assertEquals(self.y(u'google.ca'), (u'google.ca', 'Enter a valid URL'))
self.assertEquals(self.z(u'invalid.domain..com'), (
u'invalid.domain..com', 'enter a valid URL'))
u'invalid.domain..com', 'Enter a valid URL'))
self.assertEquals(self.z(u'invalid.\u4e2d\u4fd4.blargg'), (
u'invalid.\u4e2d\u4fd4.blargg', 'enter a valid URL'))
u'invalid.\u4e2d\u4fd4.blargg', 'Enter a valid URL'))
# ##############################################################################
+100 -73
View File
@@ -12,7 +12,6 @@ import tempfile
import threading
import logging
def fix_sys_path():
"""
logic to have always the correct sys.path
@@ -38,86 +37,114 @@ def fix_sys_path():
os.path.abspath(os.path.join(path, 'gluon')),
'']
[add_path_first(path) for path in paths]
fix_sys_path()
#support skipif also in python 2.6
def _skipIf(cond, message=''):
def _decorator(testcase):
if cond:
return lambda *a, **kw: None
else:
return testcase
return _decorator
if hasattr(unittest, 'skipIf'):
skipIf = unittest.skipIf
else:
skipIf = _skipIf
import languages
from storage import Storage
MP_WORKING = 0
try:
import multiprocessing
def read_write(args):
(filename, iterations) = args
for i in range(0, iterations):
content = languages.read_dict(filename)
if not len(content):
return False
languages.write_dict(filename, content)
return True
class TestLanguagesParallel(unittest.TestCase):
def setUp(self):
self.filename = tempfile.mktemp()
contents = dict()
for i in range(1000):
contents["key%d" % i] = "value%d" % i
languages.write_dict(self.filename, contents)
languages.read_dict(self.filename)
def tearDown(self):
try:
os.remove(self.filename)
except:
pass
def test_reads_and_writes(self):
readwriters = 10
pool = multiprocessing.Pool(processes=readwriters)
results = pool.map(read_write, [[self.filename, 10]] * readwriters)
for result in results:
self.assertTrue(result)
class TestTranslations(unittest.TestCase):
def setUp(self):
if os.path.isdir('gluon'):
self.langpath = 'applications/welcome/languages'
else:
self.langpath = os.path.realpath(
'../../applications/welcome/languages')
self.http_accept_language = 'en'
def tearDown(self):
pass
def test_plain(self):
T = languages.translator(self.langpath, self.http_accept_language)
self.assertEqual(str(T('Hello World')),
'Hello World')
self.assertEqual(str(T('Hello World## comment')),
'Hello World')
self.assertEqual(str(T('%s %%{shop}', 1)),
'1 shop')
self.assertEqual(str(T('%s %%{shop}', 2)),
'2 shops')
self.assertEqual(str(T('%s %%{shop[0]}', 1)),
'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**')),
'<strong>Hello World</strong>')
T.force('it')
self.assertEqual(str(T('Hello World')),
'Salve Mondo')
MP_WORKING = 1
#due to http://bugs.python.org/issue10845, testing multiprocessing in python is impossible
if sys.platform.startswith('win'):
MP_WORKING = 0
except ImportError:
logging.warning("Skipped test case, no multiprocessing module.")
pass
def read_write(args):
(filename, iterations) = args
for i in range(0, iterations):
content = languages.read_dict(filename)
if not len(content):
return False
languages.write_dict(filename, content)
return True
class TestLanguagesParallel(unittest.TestCase):
def setUp(self):
self.filename = tempfile.mktemp()
contents = dict()
for i in range(1000):
contents["key%d" % i] = "value%d" % i
languages.write_dict(self.filename, contents)
languages.read_dict(self.filename)
def tearDown(self):
try:
os.remove(self.filename)
except:
pass
@skipIf(MP_WORKING == 0, 'multiprocessing tests unavailable')
def test_reads_and_writes(self):
readwriters = 10
pool = multiprocessing.Pool(processes=readwriters)
results = pool.map(read_write, [[self.filename, 10]] * readwriters)
for result in results:
self.assertTrue(result)
@skipIf(MP_WORKING == 1, 'multiprocessing tests available')
def test_reads_and_writes_no_mp(self):
results = []
for i in range(10):
results.append(read_write([self.filename, 10]))
for result in results:
self.assertTrue(result)
class TestTranslations(unittest.TestCase):
def setUp(self):
if os.path.isdir('gluon'):
self.langpath = 'applications/welcome/languages'
else:
self.langpath = os.path.realpath(
'../../applications/welcome/languages')
self.http_accept_language = 'en'
def tearDown(self):
pass
def test_plain(self):
T = languages.translator(self.langpath, self.http_accept_language)
self.assertEqual(str(T('Hello World')),
'Hello World')
self.assertEqual(str(T('Hello World## comment')),
'Hello World')
self.assertEqual(str(T('%s %%{shop}', 1)),
'1 shop')
self.assertEqual(str(T('%s %%{shop}', 2)),
'2 shops')
self.assertEqual(str(T('%s %%{shop[0]}', 1)),
'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**')),
'<strong>Hello World</strong>')
T.force('it')
self.assertEqual(str(T('Hello World')),
'Salve Mondo')
if __name__ == '__main__':
unittest.main()
+97 -39
View File
@@ -12,7 +12,7 @@ import logging
if os.path.isdir('gluon'):
sys.path.insert(0,os.path.realpath('gluon')) # running from web2py base
else:
sys.path.insert(0,os.path.realpath('../')) # running from gluon/tests/
sys.path.insert(0,os.path.realpath('../../')) # running from gluon/tests/
os.environ['web2py_path'] = os.path.realpath('../../') # for settings
from gluon.rewrite import load, filter_url, filter_err, get_effective_router, map_url_out
@@ -26,6 +26,8 @@ logger = None
oldcwd = None
root = None
def norm_root(root):
return root.replace('/', os.sep)
def setUpModule():
def make_apptree():
@@ -71,7 +73,7 @@ def setUpModule():
if not os.path.isdir('gluon'):
os.chdir(os.path.realpath(
'../../')) # run from web2py base directory
import main # for initialization after chdir
import gluon.main # for initialization after chdir
global logger
logger = logging.getLogger('web2py.rewrite')
global_settings.applications_parent = tempfile.mkdtemp()
@@ -120,7 +122,7 @@ class TestRouter(unittest.TestCase):
self.assertEqual(filter_url('http://domain.com/', app=True), 'init')
# incoming
self.assertEqual(filter_url('http://domain.com/favicon.ico'),
'%s/applications/init/static/favicon.ico' % root)
norm_root('%s/applications/init/static/favicon.ico' % root))
self.assertEqual(
filter_url('http://domain.com/abc'), '/init/default/abc')
self.assertEqual(filter_url(
@@ -129,7 +131,8 @@ class TestRouter(unittest.TestCase):
'http://domain.com/abc/def'), "/init/default/abc ['def']")
self.assertEqual(filter_url(
'http://domain.com/index/a%20bc'), "/init/default/index ['a bc']")
self.assertEqual(filter_url('http://domain.com/welcome/static/path/to/static'), "%s/applications/welcome/static/path/to/static" % root)
self.assertEqual(filter_url('http://domain.com/welcome/static/path/to/static').replace('/', os.sep),
norm_root("%s/applications/welcome/static/path/to/static" % root))
self.assertRaises(HTTP, filter_url, 'http://domain.com/welcome/static/bad/path/to/st~tic')
try:
# 2.7+ only
@@ -164,8 +167,8 @@ class TestRouter(unittest.TestCase):
load(rdict=dict())
self.assertEqual(
filter_url('http://domain.com/welcome'), '/welcome/default/index')
self.assertEqual(filter_url(
'http://domain.com/examples'), '/examples/default/exdef')
self.assertEqual(
filter_url('http://domain.com/examples'), '/examples/default/exdef')
def test_router_defapp(self):
""" Test the default-application function """
@@ -189,10 +192,11 @@ class TestRouter(unittest.TestCase):
self.assertEqual(filter_url(
'http://domain.com/abc/def'), "/welcome/default/abc ['def']")
self.assertEqual(filter_url('http://domain.com/favicon.ico'),
'%s/applications/welcome/static/favicon.ico' % root)
norm_root('%s/applications/welcome/static/favicon.ico' % root))
self.assertEqual(filter_url('http://domain.com/static/abc'),
'%s/applications/welcome/static/abc' % root)
self.assertEqual(filter_url('http://domain.com/static/path/to/static'), "%s/applications/welcome/static/path/to/static" % root)
norm_root('%s/applications/welcome/static/abc' % root))
self.assertEqual(filter_url('http://domain.com/static/path/to/static').replace('/', os.sep),
norm_root("%s/applications/welcome/static/path/to/static" % root))
# outgoing
self.assertEqual(filter_url(
'http://domain.com/welcome/default/index', out=True), '/')
@@ -1021,7 +1025,7 @@ class TestRouter(unittest.TestCase):
'http://domain.com/init/default/fcn_1', out=True), "/fcn_1")
self.assertEqual(
filter_url('http://domain.com/static/filename-with_underscore'),
"%s/applications/init/static/filename-with_underscore" % root)
norm_root("%s/applications/init/static/filename-with_underscore" % root))
self.assertEqual(
filter_url('http://domain.com/init/static/filename-with_underscore', out=True),
"/init/static/filename-with_underscore")
@@ -1038,7 +1042,7 @@ class TestRouter(unittest.TestCase):
self.assertEqual(
filter_url(
'http://domain.com/app2/static/filename-with_underscore'),
"%s/applications/app2/static/filename-with_underscore" % root)
norm_root("%s/applications/app2/static/filename-with_underscore" % root))
self.assertEqual(str(URL(a='init', c='default', f='a_b')), "/a_b")
self.assertEqual(str(URL(a='app1', c='default', f='a_b')), "/app1/a-b")
@@ -1079,14 +1083,17 @@ class TestRouter(unittest.TestCase):
self.assertEqual(filter_url('http://domain.com/index/a%20bc'),
"/admin/default/index ['a bc'] (en)")
self.assertEqual(filter_url('http://domain.com/static/file'),
"%s/applications/admin/static/file" % root)
norm_root("%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)
norm_root("%s/applications/admin/static/file" % root))
self.assertEqual(filter_url('http://domain.com/examples/en/static/file'),
norm_root("%s/applications/examples/static/en/file" % root))
self.assertEqual(filter_url('http://domain.com/examples/static/file'),
"%s/applications/examples/static/en/file" % root)
self.assertEqual(filter_url('http://domain.com/examples/it/static/file'), "%s/applications/examples/static/it/file" % root)
self.assertEqual(filter_url('http://domain.com/examples/it-it/static/file'), "%s/applications/examples/static/file" % root)
norm_root("%s/applications/examples/static/en/file" % root))
self.assertEqual(filter_url('http://domain.com/examples/it/static/file'),
norm_root("%s/applications/examples/static/it/file" % root))
self.assertEqual(filter_url('http://domain.com/examples/it-it/static/file'),
norm_root("%s/applications/examples/static/file" % root))
self.assertEqual(filter_url('https://domain.com/admin/ctr/fcn',
lang='en', out=True), "/ctr/fcn")
@@ -1105,6 +1112,40 @@ class TestRouter(unittest.TestCase):
self.assertEqual(filter_url('https://domain.com/welcome/ctr/fcn',
lang='es', out=True), "/welcome/ctr/fcn")
self.assertEqual(filter_url('https://domain.com/admin/ctr/fcn',
language='en', out=True), "/ctr/fcn")
self.assertEqual(filter_url('https://domain.com/admin/ctr/fcn',
language='it', out=True), "/it/ctr/fcn")
self.assertEqual(filter_url('https://domain.com/admin/ctr/fcn',
language='it-it', out=True), "/it-it/ctr/fcn")
self.assertEqual(filter_url('https://domain.com/admin/static/file',
language='en', out=True), "/admin/en/static/file")
self.assertEqual(filter_url('https://domain.com/admin/static/file',
language='it', out=True), "/admin/it/static/file")
self.assertEqual(filter_url('https://domain.com/admin/static/file',
language='it-it', out=True), "/admin/it-it/static/file")
self.assertEqual(filter_url('https://domain.com/welcome/ctr/fcn',
language='it', out=True), "/welcome/ctr/fcn")
self.assertEqual(filter_url('https://domain.com/welcome/ctr/fcn',
language='es', out=True), "/welcome/ctr/fcn")
self.assertEqual(filter_url('https://domain.com/admin/ctr/fcn',
lang='it', language='en', out=True), "/ctr/fcn")
self.assertEqual(filter_url('https://domain.com/admin/ctr/fcn',
lang='en', language='it', out=True), "/it/ctr/fcn")
self.assertEqual(filter_url('https://domain.com/admin/ctr/fcn',
lang='it', language='it-it', out=True), "/it-it/ctr/fcn")
self.assertEqual(filter_url('https://domain.com/admin/static/file',
lang='it', language='en', out=True), "/admin/en/static/file")
self.assertEqual(filter_url('https://domain.com/admin/static/file',
lang='it', language='it', out=True), "/admin/it/static/file")
self.assertEqual(filter_url('https://domain.com/admin/static/file',
lang='it', language='it-it', out=True), "/admin/it-it/static/file")
self.assertEqual(filter_url('https://domain.com/welcome/ctr/fcn',
lang='it', language='it', out=True), "/welcome/ctr/fcn")
self.assertEqual(filter_url('https://domain.com/welcome/ctr/fcn',
lang='it', language='es', out=True), "/welcome/ctr/fcn")
router_lang['admin']['map_static'] = True
load(rdict=router_lang)
self.assertEqual(filter_url('https://domain.com/admin/ctr/fcn',
@@ -1144,18 +1185,23 @@ class TestRouter(unittest.TestCase):
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)
norm_root("%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)
norm_root("%s/applications/admin/static/file" % root))
self.assertEqual(filter_url('http://domain.com/examples/en/static/file'),
norm_root("%s/applications/examples/static/en/file" % root))
self.assertEqual(filter_url('http://domain.com/examples/static/file'),
"%s/applications/examples/static/en/file" % root)
self.assertEqual(filter_url('http://domain.com/examples/it/static/file'), "%s/applications/examples/static/it/file" % root)
self.assertEqual(filter_url('http://domain.com/examples/it-it/static/file'), "%s/applications/examples/static/file" % root)
self.assertEqual(filter_url('http://domain.com/examples/static/en/file'), "%s/applications/examples/static/en/file" % root)
self.assertEqual(filter_url('http://domain.com/examples/static/it/file'), "%s/applications/examples/static/it/file" % root)
self.assertEqual(filter_url('http://domain.com/examples/static/it-it/file'), "%s/applications/examples/static/it-it/file" % root)
norm_root("%s/applications/examples/static/en/file" % root))
self.assertEqual(filter_url('http://domain.com/examples/it/static/file'),
norm_root("%s/applications/examples/static/it/file" % root))
self.assertEqual(filter_url('http://domain.com/examples/it-it/static/file'),
norm_root("%s/applications/examples/static/file" % root))
self.assertEqual(filter_url('http://domain.com/examples/static/en/file').replace('/', os.sep),
norm_root("%s/applications/examples/static/en/file" % root))
self.assertEqual(filter_url('http://domain.com/examples/static/it/file').replace('/', os.sep),
norm_root("%s/applications/examples/static/it/file" % root))
self.assertEqual(filter_url('http://domain.com/examples/static/it-it/file').replace('/', os.sep),
norm_root("%s/applications/examples/static/it-it/file" % root))
def test_router_get_effective(self):
'''
@@ -1221,11 +1267,15 @@ class TestRouter(unittest.TestCase):
'''
load(rdict=dict())
self.assertEqual(filter_url('http://domain.com/welcome/static/path/to/static'), "%s/applications/welcome/static/path/to/static" % root)
self.assertEqual(filter_url('http://domain.com/welcome/static/path/to/static').replace('/', os.sep),
norm_root("%s/applications/welcome/static/path/to/static" % root))
self.assertRaises(HTTP, filter_url, 'http://domain.com/welcome/static/bad/path/to/st~tic')
self.assertEqual(filter_url('http://domain.com/welcome/static/path/to--/static'), "%s/applications/welcome/static/path/to--/static" % root)
self.assertEqual(filter_url('http://domain.com/welcome/static/path/==to--/static'), "%s/applications/welcome/static/path/==to--/static" % root)
self.assertEqual(filter_url('http://domain.com/welcome/static/path/-+=@$%/static'), "%s/applications/welcome/static/path/-+=@$%%/static" % root)
self.assertEqual(filter_url('http://domain.com/welcome/static/path/to--/static').replace('/', os.sep),
norm_root("%s/applications/welcome/static/path/to--/static" % root))
self.assertEqual(filter_url('http://domain.com/welcome/static/path/==to--/static').replace('/', os.sep),
norm_root("%s/applications/welcome/static/path/==to--/static" % root))
self.assertEqual(filter_url('http://domain.com/welcome/static/path/-+=@$%/static').replace('/', os.sep),
norm_root("%s/applications/welcome/static/path/-+=@$%%/static" % root))
self.assertRaises(HTTP, filter_url, 'http://domain.com/welcome/static/bad/path/to/.static')
self.assertRaises(HTTP, filter_url, 'http://domain.com/welcome/static/bad/path/to/s..tatic')
self.assertRaises(HTTP, filter_url, 'http://domain.com/welcome/static/bad/path/to//static')
@@ -1237,7 +1287,8 @@ class TestRouter(unittest.TestCase):
),
)
load(rdict=router_static)
self.assertEqual(filter_url('http://domain.com/welcome/static/path/to/#static'), "%s/applications/welcome/static/path/to/#static" % root)
self.assertEqual(filter_url('http://domain.com/welcome/static/path/to/#static').replace('/', os.sep),
norm_root("%s/applications/welcome/static/path/to/#static" % root))
router_static = dict(
BASE=dict(
@@ -1245,17 +1296,24 @@ class TestRouter(unittest.TestCase):
),
)
load(rdict=router_static)
self.assertEqual(filter_url('http://domain.com/welcome/static/path/to/static'), "%s/applications/welcome/static/path/to/static" % root)
self.assertEqual(filter_url('http://domain.com/welcome/static/path/to/static').replace('/', os.sep),
norm_root("%s/applications/welcome/static/path/to/static" % root))
self.assertRaises(HTTP, filter_url, 'http://domain.com/welcome/static/bad/path/to/st~tic')
self.assertEqual(filter_url('http://domain.com/welcome/static/path/to--/static'), "%s/applications/welcome/static/path/to--/static" % root)
self.assertEqual(filter_url('http://domain.com/welcome/static/path/==to--/static'), "%s/applications/welcome/static/path/==to--/static" % root)
self.assertEqual(filter_url('http://domain.com/welcome/static/path/-+=@$%/static'), "%s/applications/welcome/static/path/-+=@$%%/static" % root)
self.assertEqual(filter_url('http://domain.com/welcome/static/path/to--/static').replace('/', os.sep),
norm_root("%s/applications/welcome/static/path/to--/static" % root))
self.assertEqual(filter_url('http://domain.com/welcome/static/path/==to--/static').replace('/', os.sep),
norm_root("%s/applications/welcome/static/path/==to--/static" % root))
self.assertEqual(filter_url('http://domain.com/welcome/static/path/-+=@$%/static').replace('/', os.sep),
norm_root("%s/applications/welcome/static/path/-+=@$%%/static" % root))
self.assertRaises(HTTP, filter_url, 'http://domain.com/welcome/static/bad/path/to//static')
self.assertEqual(filter_url('http://domain.com/welcome/static/path/to/#static'), "%s/applications/welcome/static/path/to/#static" % root)
self.assertEqual(filter_url('http://domain.com/welcome/static/path/to/#static').replace('/', os.sep),
norm_root("%s/applications/welcome/static/path/to/#static" % root))
self.assertRaises(HTTP, filter_url, 'http://domain.com/welcome/static/bad/path/./static')
self.assertRaises(HTTP, filter_url, 'http://domain.com/welcome/static/bad/path/../static')
self.assertEqual(filter_url('http://domain.com/welcome/static/path/.../static'), "%s/applications/welcome/static/path/.../static" % root)
self.assertEqual(filter_url('http://domain.com/welcome/static/path/to/.static'), "%s/applications/welcome/static/path/to/.static" % root)
self.assertEqual(filter_url('http://domain.com/welcome/static/path/.../static').replace('/', os.sep),
norm_root("%s/applications/welcome/static/path/.../static" % root))
self.assertEqual(filter_url('http://domain.com/welcome/static/path/to/.static').replace('/', os.sep),
norm_root("%s/applications/welcome/static/path/to/.static" % root))
def test_router_args(self):
'''
+9 -3
View File
@@ -27,6 +27,10 @@ oldcwd = None
root = None
def norm_root(root):
return root.replace('/', os.sep)
def setUpModule():
def make_apptree():
"build a temporary applications tree"
@@ -104,7 +108,8 @@ class TestRoutes(unittest.TestCase):
'http://domain.com/abc/def/ghi/jkl'), "/abc/def/ghi ['jkl']")
self.assertEqual(filter_url(
'http://domain.com/abc/def/ghi/j%20kl'), "/abc/def/ghi ['j_kl']")
self.assertEqual(filter_url('http://domain.com/welcome/static/path/to/static'), "%s/applications/welcome/static/path/to/static" % root)
self.assertEqual(filter_url('http://domain.com/welcome/static/path/to/static'),
norm_root("%s/applications/welcome/static/path/to/static" % root))
# no more necessary since explcit check for directory traversal attacks
"""
self.assertRaises(HTTP, filter_url, 'http://domain.com/welcome/static/bad/path/to/st~tic')
@@ -168,8 +173,9 @@ default_application = 'defapp'
filter_url('http://domain.com/app'), '/app/default/index')
self.assertEqual(filter_url('http://domain.com/welcome/default/index/abc'), "/welcome/default/index ['abc']")
self.assertEqual(filter_url('http://domain.com/welcome/static/abc'),
'%s/applications/welcome/static/abc' % root)
self.assertEqual(filter_url('http://domain.com/defapp/static/path/to/static'), "%s/applications/defapp/static/path/to/static" % root)
norm_root('%s/applications/welcome/static/abc' % root))
self.assertEqual(filter_url('http://domain.com/defapp/static/path/to/static'),
norm_root("%s/applications/defapp/static/path/to/static" % root))
def test_routes_raise(self):
'''
+106 -106
View File
@@ -60,9 +60,9 @@ class TestValidators(unittest.TestCase):
rtn = ANY_OF([IS_EMAIL(),IS_ALPHANUMERIC()])('abco')
self.assertEqual(rtn, ('abco', None))
rtn = ANY_OF([IS_EMAIL(),IS_ALPHANUMERIC()])('@ab.co')
self.assertEqual(rtn, ('@ab.co', 'enter only letters, numbers, and underscore'))
self.assertEqual(rtn, ('@ab.co', 'Enter only letters, numbers, and underscore'))
rtn = ANY_OF([IS_ALPHANUMERIC(),IS_EMAIL()])('@ab.co')
self.assertEqual(rtn, ('@ab.co', 'enter a valid email address'))
self.assertEqual(rtn, ('@ab.co', 'Enter a valid email address'))
def test_CLEANUP(self):
rtn = CLEANUP()('helloò')
@@ -99,7 +99,7 @@ class TestValidators(unittest.TestCase):
rtn = IS_ALPHANUMERIC()('A_a')
self.assertEqual(rtn, ('A_a', None))
rtn = IS_ALPHANUMERIC()('!')
self.assertEqual(rtn, ('!', 'enter only letters, numbers, and underscore'))
self.assertEqual(rtn, ('!', 'Enter only letters, numbers, and underscore'))
def test_IS_DATE_IN_RANGE(self):
v = IS_DATE_IN_RANGE(minimum=datetime.date(2008,1,1),
@@ -151,39 +151,39 @@ class TestValidators(unittest.TestCase):
rtn = IS_DECIMAL_IN_RANGE(1,5)(1)
self.assertEqual(rtn, (decimal.Decimal('1'), None))
rtn = IS_DECIMAL_IN_RANGE(1,5)(5.25)
self.assertEqual(rtn, (5.25, 'enter a number between 1 and 5'))
self.assertEqual(rtn, (5.25, 'Enter a number between 1 and 5'))
rtn = IS_DECIMAL_IN_RANGE(5.25,6)(5.25)
self.assertEqual(rtn, (decimal.Decimal('5.25'), None))
rtn = IS_DECIMAL_IN_RANGE(5.25,6)('5.25')
self.assertEqual(rtn, (decimal.Decimal('5.25'), None))
rtn = IS_DECIMAL_IN_RANGE(1,5)(6.0)
self.assertEqual(rtn, (6.0, 'enter a number between 1 and 5'))
self.assertEqual(rtn, (6.0, 'Enter a number between 1 and 5'))
rtn = IS_DECIMAL_IN_RANGE(1,5)(3.5)
self.assertEqual(rtn, (decimal.Decimal('3.5'), None))
rtn = IS_DECIMAL_IN_RANGE(1.5,5.5)(3.5)
self.assertEqual(rtn, (decimal.Decimal('3.5'), None))
rtn = IS_DECIMAL_IN_RANGE(1.5,5.5)(6.5)
self.assertEqual(rtn, (6.5, 'enter a number between 1.5 and 5.5'))
self.assertEqual(rtn, (6.5, 'Enter a number between 1.5 and 5.5'))
rtn = IS_DECIMAL_IN_RANGE(1.5,None)(6.5)
self.assertEqual(rtn, (decimal.Decimal('6.5'), None))
rtn = IS_DECIMAL_IN_RANGE(1.5,None)(0.5)
self.assertEqual(rtn, (0.5, 'enter a number greater than or equal to 1.5'))
self.assertEqual(rtn, (0.5, 'Enter a number greater than or equal to 1.5'))
rtn = IS_DECIMAL_IN_RANGE(None,5.5)(4.5)
self.assertEqual(rtn, (decimal.Decimal('4.5'), None))
rtn = IS_DECIMAL_IN_RANGE(None,5.5)(6.5)
self.assertEqual(rtn, (6.5, 'enter a number less than or equal to 5.5'))
self.assertEqual(rtn, (6.5, 'Enter a number less than or equal to 5.5'))
rtn = IS_DECIMAL_IN_RANGE()(6.5)
self.assertEqual(rtn, (decimal.Decimal('6.5'), None))
rtn = IS_DECIMAL_IN_RANGE(0,99)(123.123)
self.assertEqual(rtn, (123.123, 'enter a number between 0 and 99'))
self.assertEqual(rtn, (123.123, 'Enter a number between 0 and 99'))
rtn = IS_DECIMAL_IN_RANGE(0,99)('123.123')
self.assertEqual(rtn, ('123.123', 'enter a number between 0 and 99'))
self.assertEqual(rtn, ('123.123', 'Enter a number between 0 and 99'))
rtn = IS_DECIMAL_IN_RANGE(0,99)('12.34')
self.assertEqual(rtn, (decimal.Decimal('12.34'), None))
rtn = IS_DECIMAL_IN_RANGE()('abc')
self.assertEqual(rtn, ('abc', 'enter a number'))
self.assertEqual(rtn, ('abc', 'Enter a number'))
rtn = IS_DECIMAL_IN_RANGE()('6,5')
self.assertEqual(rtn, ('6,5', 'enter a number'))
self.assertEqual(rtn, ('6,5', 'Enter a number'))
rtn = IS_DECIMAL_IN_RANGE(dot=',')('6.5')
self.assertEqual(rtn, (decimal.Decimal('6.5'), None))
@@ -199,25 +199,25 @@ class TestValidators(unittest.TestCase):
rtn = IS_EMAIL()('abc@d_-f.us')
self.assertEqual(rtn, ('abc@d_-f.us', None))
rtn = IS_EMAIL()('@def.com') # missing name
self.assertEqual(rtn, ('@def.com', 'enter a valid email address'))
self.assertEqual(rtn, ('@def.com', 'Enter a valid email address'))
rtn = IS_EMAIL()('"abc@def".com') # quoted name
self.assertEqual(rtn, ('"abc@def".com', 'enter a valid email address'))
self.assertEqual(rtn, ('"abc@def".com', 'Enter a valid email address'))
rtn = IS_EMAIL()('abc+def.com') # no @
self.assertEqual(rtn, ('abc+def.com', 'enter a valid email address'))
self.assertEqual(rtn, ('abc+def.com', 'Enter a valid email address'))
rtn = IS_EMAIL()('abc@def.x') # one-char TLD
self.assertEqual(rtn, ('abc@def.x', 'enter a valid email address'))
self.assertEqual(rtn, ('abc@def.x', 'Enter a valid email address'))
rtn = IS_EMAIL()('abc@def.12') # numeric TLD
self.assertEqual(rtn, ('abc@def.12', 'enter a valid email address'))
self.assertEqual(rtn, ('abc@def.12', 'Enter a valid email address'))
rtn = IS_EMAIL()('abc@def..com') # double-dot in domain
self.assertEqual(rtn, ('abc@def..com', 'enter a valid email address'))
self.assertEqual(rtn, ('abc@def..com', 'Enter a valid email address'))
rtn = IS_EMAIL()('abc@.def.com') # dot starts domain
self.assertEqual(rtn, ('abc@.def.com', 'enter a valid email address'))
self.assertEqual(rtn, ('abc@.def.com', 'Enter a valid email address'))
rtn = IS_EMAIL()('abc@def.c_m') # underscore in TLD
self.assertEqual(rtn, ('abc@def.c_m', 'enter a valid email address'))
self.assertEqual(rtn, ('abc@def.c_m', 'Enter a valid email address'))
rtn = IS_EMAIL()('NotAnEmail') # missing @
self.assertEqual(rtn, ('NotAnEmail', 'enter a valid email address'))
self.assertEqual(rtn, ('NotAnEmail', 'Enter a valid email address'))
rtn = IS_EMAIL()('abc@NotAnEmail') # missing TLD
self.assertEqual(rtn, ('abc@NotAnEmail', 'enter a valid email address'))
self.assertEqual(rtn, ('abc@NotAnEmail', 'Enter a valid email address'))
rtn = IS_EMAIL()('customer/department@example.com')
self.assertEqual(rtn, ('customer/department@example.com', None))
rtn = IS_EMAIL()('$A12345@example.com')
@@ -229,17 +229,17 @@ class TestValidators(unittest.TestCase):
rtn = IS_EMAIL()('~@example.com')
self.assertEqual(rtn, ('~@example.com', None))
rtn = IS_EMAIL()('.wooly@example.com') # dot starts name
self.assertEqual(rtn, ('.wooly@example.com', 'enter a valid email address'))
self.assertEqual(rtn, ('.wooly@example.com', 'Enter a valid email address'))
rtn = IS_EMAIL()('wo..oly@example.com') # adjacent dots in name
self.assertEqual(rtn, ('wo..oly@example.com', 'enter a valid email address'))
self.assertEqual(rtn, ('wo..oly@example.com', 'Enter a valid email address'))
rtn = IS_EMAIL()('pootietang.@example.com') # dot ends name
self.assertEqual(rtn, ('pootietang.@example.com', 'enter a valid email address'))
self.assertEqual(rtn, ('pootietang.@example.com', 'Enter a valid email address'))
rtn = IS_EMAIL()('.@example.com') # name is bare dot
self.assertEqual(rtn, ('.@example.com', 'enter a valid email address'))
self.assertEqual(rtn, ('.@example.com', 'Enter a valid email address'))
rtn = IS_EMAIL()('Ima.Fool@example.com')
self.assertEqual(rtn, ('Ima.Fool@example.com', None))
rtn = IS_EMAIL()('Ima Fool@example.com') # space in name
self.assertEqual(rtn, ('Ima Fool@example.com', 'enter a valid email address'))
self.assertEqual(rtn, ('Ima Fool@example.com', 'Enter a valid email address'))
rtn = IS_EMAIL()('localguy@localhost') # localhost as domain
self.assertEqual(rtn, ('localguy@localhost', None))
@@ -266,15 +266,15 @@ class TestValidators(unittest.TestCase):
rtn = IS_EMPTY_OR(IS_EMAIL(), null='abc', empty_regex='def')('def')
self.assertEqual(rtn, ('abc', None))
rtn = IS_EMPTY_OR(IS_EMAIL())('abc')
self.assertEqual(rtn, ('abc', 'enter a valid email address'))
self.assertEqual(rtn, ('abc', 'Enter a valid email address'))
rtn = IS_EMPTY_OR(IS_EMAIL())(' abc ')
self.assertEqual(rtn, ('abc', 'enter a valid email address'))
self.assertEqual(rtn, ('abc', 'Enter a valid email address'))
def test_IS_EXPR(self):
rtn = IS_EXPR('int(value) < 2')('1')
self.assertEqual(rtn, ('1', None))
rtn = IS_EXPR('int(value) < 2')('2')
self.assertEqual(rtn, ('2', 'invalid expression'))
self.assertEqual(rtn, ('2', 'Invalid expression'))
rtn = IS_EXPR(lambda value: int(value))('1')
self.assertEqual(rtn, ('1', 1))
rtn = IS_EXPR(lambda value: int(value) < 2 and 'invalid' or None)('2')
@@ -288,9 +288,9 @@ class TestValidators(unittest.TestCase):
rtn = IS_FLOAT_IN_RANGE(1,5)(1)
self.assertEqual(rtn, (1.0, None))
rtn = IS_FLOAT_IN_RANGE(1,5)(5.25)
self.assertEqual(rtn, (5.25, 'enter a number between 1 and 5'))
self.assertEqual(rtn, (5.25, 'Enter a number between 1 and 5'))
rtn = IS_FLOAT_IN_RANGE(1,5)(6.0)
self.assertEqual(rtn, (6.0, 'enter a number between 1 and 5'))
self.assertEqual(rtn, (6.0, 'Enter a number between 1 and 5'))
rtn = IS_FLOAT_IN_RANGE(1,5)(3.5)
self.assertEqual(rtn, (3.5, None))
rtn = IS_FLOAT_IN_RANGE(1,None)(3.5)
@@ -298,15 +298,15 @@ class TestValidators(unittest.TestCase):
rtn = IS_FLOAT_IN_RANGE(None,5)(3.5)
self.assertEqual(rtn, (3.5, None))
rtn = IS_FLOAT_IN_RANGE(1,None)(0.5)
self.assertEqual(rtn, (0.5, 'enter a number greater than or equal to 1'))
self.assertEqual(rtn, (0.5, 'Enter a number greater than or equal to 1'))
rtn = IS_FLOAT_IN_RANGE(None,5)(6.5)
self.assertEqual(rtn, (6.5, 'enter a number less than or equal to 5'))
self.assertEqual(rtn, (6.5, 'Enter a number less than or equal to 5'))
rtn = IS_FLOAT_IN_RANGE()(6.5)
self.assertEqual(rtn, (6.5, None))
rtn = IS_FLOAT_IN_RANGE()('abc')
self.assertEqual(rtn, ('abc', 'enter a number'))
self.assertEqual(rtn, ('abc', 'Enter a number'))
rtn = IS_FLOAT_IN_RANGE()('6,5')
self.assertEqual(rtn, ('6,5', 'enter a number'))
self.assertEqual(rtn, ('6,5', 'Enter a number'))
rtn = IS_FLOAT_IN_RANGE(dot=',')('6.5')
self.assertEqual(rtn, (6.5, None))
@@ -315,11 +315,11 @@ class TestValidators(unittest.TestCase):
rtn = IS_IN_SET(['max', 'john'])('max')
self.assertEqual(rtn, ('max', None))
rtn = IS_IN_SET(['max', 'john'])('massimo')
self.assertEqual(rtn, ('massimo', 'value not allowed'))
self.assertEqual(rtn, ('massimo', 'Value not allowed'))
rtn = IS_IN_SET(['max', 'john'], multiple=True)(('max', 'john'))
self.assertEqual(rtn, (('max', 'john'), None))
rtn = IS_IN_SET(['max', 'john'], multiple=True)(('bill', 'john'))
self.assertEqual(rtn, (('bill', 'john'), 'value not allowed'))
self.assertEqual(rtn, (('bill', 'john'), 'Value not allowed'))
rtn = IS_IN_SET(('id1','id2'), ['first label','second label'])('id1') # Traditional way
self.assertEqual(rtn, ('id1', None))
rtn = IS_IN_SET({'id1':'first label', 'id2':'second label'})('id1')
@@ -338,23 +338,23 @@ class TestValidators(unittest.TestCase):
rtn = IS_INT_IN_RANGE(1,5)(1)
self.assertEqual(rtn, (1, None))
rtn = IS_INT_IN_RANGE(1,5)(5)
self.assertEqual(rtn, (5, 'enter an integer between 1 and 4'))
self.assertEqual(rtn, (5, 'Enter an integer between 1 and 4'))
rtn = IS_INT_IN_RANGE(1,5)(5)
self.assertEqual(rtn, (5, 'enter an integer between 1 and 4'))
self.assertEqual(rtn, (5, 'Enter an integer between 1 and 4'))
rtn = IS_INT_IN_RANGE(1,5)(3.5)
self.assertEqual(rtn, (3.5, 'enter an integer between 1 and 4'))
self.assertEqual(rtn, (3.5, 'Enter an integer between 1 and 4'))
rtn = IS_INT_IN_RANGE(None,5)('4')
self.assertEqual(rtn, (4, None))
rtn = IS_INT_IN_RANGE(None,5)('6')
self.assertEqual(rtn, ('6', 'enter an integer less than or equal to 4'))
self.assertEqual(rtn, ('6', 'Enter an integer less than or equal to 4'))
rtn = IS_INT_IN_RANGE(1,None)('4')
self.assertEqual(rtn, (4, None))
rtn = IS_INT_IN_RANGE(1,None)('0')
self.assertEqual(rtn, ('0', 'enter an integer greater than or equal to 1'))
self.assertEqual(rtn, ('0', 'Enter an integer greater than or equal to 1'))
rtn = IS_INT_IN_RANGE()(6)
self.assertEqual(rtn, (6, None))
rtn = IS_INT_IN_RANGE()('abc')
self.assertEqual(rtn, ('abc', 'enter an integer'))
self.assertEqual(rtn, ('abc', 'Enter an integer'))
def test_IS_IPV4(self):
rtn = IS_IPV4()('1.2.3.4')
@@ -362,19 +362,19 @@ class TestValidators(unittest.TestCase):
rtn = IS_IPV4()('255.255.255.255')
self.assertEqual(rtn, ('255.255.255.255', None))
rtn = IS_IPV4()('1.2.3.4 ')
self.assertEqual(rtn, ('1.2.3.4 ', 'enter valid IPv4 address'))
self.assertEqual(rtn, ('1.2.3.4 ', 'Enter valid IPv4 address'))
rtn = IS_IPV4()('1.2.3.4.5')
self.assertEqual(rtn, ('1.2.3.4.5', 'enter valid IPv4 address'))
self.assertEqual(rtn, ('1.2.3.4.5', 'Enter valid IPv4 address'))
rtn = IS_IPV4()('123.123')
self.assertEqual(rtn, ('123.123', 'enter valid IPv4 address'))
self.assertEqual(rtn, ('123.123', 'Enter valid IPv4 address'))
rtn = IS_IPV4()('1111.2.3.4')
self.assertEqual(rtn, ('1111.2.3.4', 'enter valid IPv4 address'))
self.assertEqual(rtn, ('1111.2.3.4', 'Enter valid IPv4 address'))
rtn = IS_IPV4()('0111.2.3.4')
self.assertEqual(rtn, ('0111.2.3.4', 'enter valid IPv4 address'))
self.assertEqual(rtn, ('0111.2.3.4', 'Enter valid IPv4 address'))
rtn = IS_IPV4()('256.2.3.4')
self.assertEqual(rtn, ('256.2.3.4', 'enter valid IPv4 address'))
self.assertEqual(rtn, ('256.2.3.4', 'Enter valid IPv4 address'))
rtn = IS_IPV4()('300.2.3.4')
self.assertEqual(rtn, ('300.2.3.4', 'enter valid IPv4 address'))
self.assertEqual(rtn, ('300.2.3.4', 'Enter valid IPv4 address'))
rtn = IS_IPV4(minip='1.2.3.4', maxip='1.2.3.4')('1.2.3.4')
self.assertEqual(rtn, ('1.2.3.4', None))
rtn = IS_IPV4(minip='1.2.3.5', maxip='1.2.3.9', error_message='bad ip')('1.2.3.4')
@@ -382,41 +382,41 @@ class TestValidators(unittest.TestCase):
rtn = IS_IPV4(maxip='1.2.3.4', invert=True)('127.0.0.1')
self.assertEqual(rtn, ('127.0.0.1', None))
rtn = IS_IPV4(maxip='1.2.3.4', invert=True)('1.2.3.4')
self.assertEqual(rtn, ('1.2.3.4', 'enter valid IPv4 address'))
self.assertEqual(rtn, ('1.2.3.4', 'Enter valid IPv4 address'))
rtn = IS_IPV4(is_localhost=True)('127.0.0.1')
self.assertEqual(rtn, ('127.0.0.1', None))
rtn = IS_IPV4(is_localhost=True)('1.2.3.4')
self.assertEqual(rtn, ('1.2.3.4', 'enter valid IPv4 address'))
self.assertEqual(rtn, ('1.2.3.4', 'Enter valid IPv4 address'))
rtn = IS_IPV4(is_localhost=False)('127.0.0.1')
self.assertEqual(rtn, ('127.0.0.1', 'enter valid IPv4 address'))
self.assertEqual(rtn, ('127.0.0.1', 'Enter valid IPv4 address'))
rtn = IS_IPV4(maxip='100.0.0.0', is_localhost=True)('127.0.0.1')
self.assertEqual(rtn, ('127.0.0.1', 'enter valid IPv4 address'))
self.assertEqual(rtn, ('127.0.0.1', 'Enter valid IPv4 address'))
def test_IS_IPV6(self):
rtn = IS_IPV6()('fe80::126c:8ffa:fe22:b3af')
self.assertEqual(rtn, ('fe80::126c:8ffa:fe22:b3af', None))
rtn = IS_IPV6()('192.168.1.1')
self.assertEqual(rtn, ('192.168.1.1', 'enter valid IPv6 address'))
self.assertEqual(rtn, ('192.168.1.1', 'Enter valid IPv6 address'))
rtn = IS_IPV6(error_message='bad ip')('192.168.1.1')
self.assertEqual(rtn, ('192.168.1.1', 'bad ip'))
rtn = IS_IPV6(is_link_local=True)('fe80::126c:8ffa:fe22:b3af')
self.assertEqual(rtn, ('fe80::126c:8ffa:fe22:b3af', None))
rtn = IS_IPV6(is_link_local=False)('fe80::126c:8ffa:fe22:b3af')
self.assertEqual(rtn, ('fe80::126c:8ffa:fe22:b3af', 'enter valid IPv6 address'))
self.assertEqual(rtn, ('fe80::126c:8ffa:fe22:b3af', 'Enter valid IPv6 address'))
rtn = IS_IPV6(is_link_local=True)('2001::126c:8ffa:fe22:b3af')
self.assertEqual(rtn, ('2001::126c:8ffa:fe22:b3af', 'enter valid IPv6 address'))
self.assertEqual(rtn, ('2001::126c:8ffa:fe22:b3af', 'Enter valid IPv6 address'))
rtn = IS_IPV6(is_multicast=True)('2001::126c:8ffa:fe22:b3af')
self.assertEqual(rtn, ('2001::126c:8ffa:fe22:b3af', 'enter valid IPv6 address'))
self.assertEqual(rtn, ('2001::126c:8ffa:fe22:b3af', 'Enter valid IPv6 address'))
rtn = IS_IPV6(is_multicast=True)('ff00::126c:8ffa:fe22:b3af')
self.assertEqual(rtn, ('ff00::126c:8ffa:fe22:b3af', None))
rtn = IS_IPV6(is_routeable=True)('2001::126c:8ffa:fe22:b3af')
self.assertEqual(rtn, ('2001::126c:8ffa:fe22:b3af', None))
rtn = IS_IPV6(is_routeable=True)('ff00::126c:8ffa:fe22:b3af')
self.assertEqual(rtn, ('ff00::126c:8ffa:fe22:b3af', 'enter valid IPv6 address'))
self.assertEqual(rtn, ('ff00::126c:8ffa:fe22:b3af', 'Enter valid IPv6 address'))
rtn = IS_IPV6(subnets='2001::/32')('2001::8ffa:fe22:b3af')
self.assertEqual(rtn, ('2001::8ffa:fe22:b3af', None))
rtn = IS_IPV6(subnets='fb00::/8')('2001::8ffa:fe22:b3af')
self.assertEqual(rtn, ('2001::8ffa:fe22:b3af', 'enter valid IPv6 address'))
self.assertEqual(rtn, ('2001::8ffa:fe22:b3af', 'Enter valid IPv6 address'))
rtn = IS_IPV6(subnets=['fc00::/8','2001::/32'])('2001::8ffa:fe22:b3af')
self.assertEqual(rtn, ('2001::8ffa:fe22:b3af', None))
rtn = IS_IPV6(subnets='invalidsubnet')('2001::8ffa:fe22:b3af')
@@ -430,19 +430,19 @@ class TestValidators(unittest.TestCase):
rtn = IS_IPADDRESS()('255.255.255.255')
self.assertEqual(rtn, ('255.255.255.255', None))
rtn = IS_IPADDRESS()('192.168.1.5 ')
self.assertEqual(rtn, ('192.168.1.5 ', 'enter valid IP address'))
self.assertEqual(rtn, ('192.168.1.5 ', 'Enter valid IP address'))
rtn = IS_IPADDRESS()('192.168.1.1.5')
self.assertEqual(rtn, ('192.168.1.1.5', 'enter valid IP address'))
self.assertEqual(rtn, ('192.168.1.1.5', 'Enter valid IP address'))
rtn = IS_IPADDRESS()('123.123')
self.assertEqual(rtn, ('123.123', 'enter valid IP address'))
self.assertEqual(rtn, ('123.123', 'Enter valid IP address'))
rtn = IS_IPADDRESS()('1111.2.3.4')
self.assertEqual(rtn, ('1111.2.3.4', 'enter valid IP address'))
self.assertEqual(rtn, ('1111.2.3.4', 'Enter valid IP address'))
rtn = IS_IPADDRESS()('0111.2.3.4')
self.assertEqual(rtn, ('0111.2.3.4', 'enter valid IP address'))
self.assertEqual(rtn, ('0111.2.3.4', 'Enter valid IP address'))
rtn = IS_IPADDRESS()('256.2.3.4')
self.assertEqual(rtn, ('256.2.3.4', 'enter valid IP address'))
self.assertEqual(rtn, ('256.2.3.4', 'Enter valid IP address'))
rtn = IS_IPADDRESS()('300.2.3.4')
self.assertEqual(rtn, ('300.2.3.4', 'enter valid IP address'))
self.assertEqual(rtn, ('300.2.3.4', 'Enter valid IP address'))
rtn = IS_IPADDRESS(minip='192.168.1.0', maxip='192.168.1.255')('192.168.1.100')
self.assertEqual(rtn, ('192.168.1.100', None))
rtn = IS_IPADDRESS(minip='1.2.3.5', maxip='1.2.3.9', error_message='bad ip')('1.2.3.4')
@@ -450,17 +450,17 @@ class TestValidators(unittest.TestCase):
rtn = IS_IPADDRESS(maxip='1.2.3.4', invert=True)('127.0.0.1')
self.assertEqual(rtn, ('127.0.0.1', None))
rtn = IS_IPADDRESS(maxip='192.168.1.4', invert=True)('192.168.1.4')
self.assertEqual(rtn, ('192.168.1.4', 'enter valid IP address'))
self.assertEqual(rtn, ('192.168.1.4', 'Enter valid IP address'))
rtn = IS_IPADDRESS(is_localhost=True)('127.0.0.1')
self.assertEqual(rtn, ('127.0.0.1', None))
rtn = IS_IPADDRESS(is_localhost=True)('192.168.1.10')
self.assertEqual(rtn, ('192.168.1.10', 'enter valid IP address'))
self.assertEqual(rtn, ('192.168.1.10', 'Enter valid IP address'))
rtn = IS_IPADDRESS(is_localhost=False)('127.0.0.1')
self.assertEqual(rtn, ('127.0.0.1', 'enter valid IP address'))
self.assertEqual(rtn, ('127.0.0.1', 'Enter valid IP address'))
rtn = IS_IPADDRESS(maxip='100.0.0.0', is_localhost=True)('127.0.0.1')
self.assertEqual(rtn, ('127.0.0.1', 'enter valid IP address'))
self.assertEqual(rtn, ('127.0.0.1', 'Enter valid IP address'))
rtn = IS_IPADDRESS()('aaa')
self.assertEqual(rtn, ('aaa', 'enter valid IP address'))
self.assertEqual(rtn, ('aaa', 'Enter valid IP address'))
rtn = IS_IPADDRESS()('fe80::126c:8ffa:fe22:b3af')
@@ -468,31 +468,31 @@ class TestValidators(unittest.TestCase):
rtn = IS_IPADDRESS(is_ipv4=False)('fe80::126c:8ffa:fe22:b3af')
self.assertEqual(rtn, ('fe80::126c:8ffa:fe22:b3af', None))
rtn = IS_IPADDRESS()('fe80::126c:8ffa:fe22:b3af ')
self.assertEqual(rtn, ('fe80::126c:8ffa:fe22:b3af ', 'enter valid IP address'))
self.assertEqual(rtn, ('fe80::126c:8ffa:fe22:b3af ', 'Enter valid IP address'))
rtn = IS_IPADDRESS(is_ipv4=True)('fe80::126c:8ffa:fe22:b3af')
self.assertEqual(rtn, ('fe80::126c:8ffa:fe22:b3af', 'enter valid IP address'))
self.assertEqual(rtn, ('fe80::126c:8ffa:fe22:b3af', 'Enter valid IP address'))
rtn = IS_IPADDRESS(is_ipv6=True)('192.168.1.1')
self.assertEqual(rtn, ('192.168.1.1', 'enter valid IP address'))
self.assertEqual(rtn, ('192.168.1.1', 'Enter valid IP address'))
rtn = IS_IPADDRESS(is_ipv6=True, error_message='bad ip')('192.168.1.1')
self.assertEqual(rtn, ('192.168.1.1', 'bad ip'))
rtn = IS_IPADDRESS(is_link_local=True)('fe80::126c:8ffa:fe22:b3af')
self.assertEqual(rtn, ('fe80::126c:8ffa:fe22:b3af', None))
rtn = IS_IPADDRESS(is_link_local=False)('fe80::126c:8ffa:fe22:b3af')
self.assertEqual(rtn, ('fe80::126c:8ffa:fe22:b3af', 'enter valid IP address'))
self.assertEqual(rtn, ('fe80::126c:8ffa:fe22:b3af', 'Enter valid IP address'))
rtn = IS_IPADDRESS(is_link_local=True)('2001::126c:8ffa:fe22:b3af')
self.assertEqual(rtn, ('2001::126c:8ffa:fe22:b3af', 'enter valid IP address'))
self.assertEqual(rtn, ('2001::126c:8ffa:fe22:b3af', 'Enter valid IP address'))
rtn = IS_IPADDRESS(is_multicast=True)('2001::126c:8ffa:fe22:b3af')
self.assertEqual(rtn, ('2001::126c:8ffa:fe22:b3af', 'enter valid IP address'))
self.assertEqual(rtn, ('2001::126c:8ffa:fe22:b3af', 'Enter valid IP address'))
rtn = IS_IPADDRESS(is_multicast=True)('ff00::126c:8ffa:fe22:b3af')
self.assertEqual(rtn, ('ff00::126c:8ffa:fe22:b3af', None))
rtn = IS_IPADDRESS(is_routeable=True)('2001::126c:8ffa:fe22:b3af')
self.assertEqual(rtn, ('2001::126c:8ffa:fe22:b3af', None))
rtn = IS_IPADDRESS(is_routeable=True)('ff00::126c:8ffa:fe22:b3af')
self.assertEqual(rtn, ('ff00::126c:8ffa:fe22:b3af', 'enter valid IP address'))
self.assertEqual(rtn, ('ff00::126c:8ffa:fe22:b3af', 'Enter valid IP address'))
rtn = IS_IPADDRESS(subnets='2001::/32')('2001::8ffa:fe22:b3af')
self.assertEqual(rtn, ('2001::8ffa:fe22:b3af', None))
rtn = IS_IPADDRESS(subnets='fb00::/8')('2001::8ffa:fe22:b3af')
self.assertEqual(rtn, ('2001::8ffa:fe22:b3af', 'enter valid IP address'))
self.assertEqual(rtn, ('2001::8ffa:fe22:b3af', 'Enter valid IP address'))
rtn = IS_IPADDRESS(subnets=['fc00::/8','2001::/32'])('2001::8ffa:fe22:b3af')
self.assertEqual(rtn, ('2001::8ffa:fe22:b3af', None))
rtn = IS_IPADDRESS(subnets='invalidsubnet')('2001::8ffa:fe22:b3af')
@@ -504,17 +504,17 @@ class TestValidators(unittest.TestCase):
rtn = IS_LENGTH()('1234567890')
self.assertEqual(rtn, ('1234567890', None))
rtn = IS_LENGTH(maxsize=5, minsize=0)('1234567890') # too long
self.assertEqual(rtn, ('1234567890', 'enter from 0 to 5 characters'))
self.assertEqual(rtn, ('1234567890', 'Enter from 0 to 5 characters'))
rtn = IS_LENGTH(maxsize=50, minsize=20)('1234567890') # too short
self.assertEqual(rtn, ('1234567890', 'enter from 20 to 50 characters'))
self.assertEqual(rtn, ('1234567890', 'Enter from 20 to 50 characters'))
rtn = IS_LENGTH()(None)
self.assertEqual(rtn, (None, None))
rtn = IS_LENGTH(minsize=0)(None)
self.assertEqual(rtn, (None, None))
rtn = IS_LENGTH(minsize=1)(None)
self.assertEqual(rtn, (None, 'enter from 1 to 255 characters'))
self.assertEqual(rtn, (None, 'Enter from 1 to 255 characters'))
rtn = IS_LENGTH(minsize=1)([])
self.assertEqual(rtn, ([], 'enter from 1 to 255 characters'))
self.assertEqual(rtn, ([], 'Enter from 1 to 255 characters'))
rtn = IS_LENGTH(minsize=1)([1])
self.assertEqual(rtn, ([1], None))
@@ -532,7 +532,7 @@ class TestValidators(unittest.TestCase):
rtn = IS_MATCH('hell.*', strict=False)('hello')
self.assertEqual(rtn, ('hello', None))
rtn = IS_MATCH('hello')('shello')
self.assertEqual(rtn, ('shello', 'invalid expression'))
self.assertEqual(rtn, ('shello', 'Invalid expression'))
rtn = IS_MATCH('hello', search=True)('shello')
self.assertEqual(rtn, ('shello', None))
rtn = IS_MATCH('hello', search=True, strict=False)('shellox')
@@ -540,13 +540,13 @@ class TestValidators(unittest.TestCase):
rtn = IS_MATCH('.*hello.*', search=True, strict=False)('shellox')
self.assertEqual(rtn, ('shellox', None))
rtn = IS_MATCH('.+')('')
self.assertEqual(rtn, ('', 'invalid expression'))
self.assertEqual(rtn, ('', 'Invalid expression'))
rtn = IS_MATCH('hell', strict=True)('hellas')
self.assertEqual(rtn, ('hellas', 'invalid expression'))
self.assertEqual(rtn, ('hellas', 'Invalid expression'))
rtn = IS_MATCH('hell$', strict=True)('hellas')
self.assertEqual(rtn, ('hellas', 'invalid expression'))
self.assertEqual(rtn, ('hellas', 'Invalid expression'))
rtn = IS_MATCH(u'hell', is_unicode=True)('àòè')
self.assertEqual(rtn, ('\xc3\xa0\xc3\xb2\xc3\xa8', 'invalid expression'))
self.assertEqual(rtn, ('\xc3\xa0\xc3\xb2\xc3\xa8', 'Invalid expression'))
rtn = IS_MATCH(u'hell', is_unicode=True)(u'hell')
self.assertEqual(rtn, (u'hell', None))
@@ -555,7 +555,7 @@ class TestValidators(unittest.TestCase):
rtn = IS_EQUAL_TO('aaa')('aaa')
self.assertEqual(rtn, ('aaa', None))
rtn = IS_EQUAL_TO('aaa')('aab')
self.assertEqual(rtn, ('aab', 'no match'))
self.assertEqual(rtn, ('aab', 'No match'))
def test_IS_NOT_EMPTY(self):
rtn = IS_NOT_EMPTY()(1)
@@ -567,19 +567,19 @@ class TestValidators(unittest.TestCase):
rtn = IS_NOT_EMPTY()(' x ')
self.assertEqual(rtn, ('x', None))
rtn = IS_NOT_EMPTY()(None)
self.assertEqual(rtn, (None, 'enter a value'))
self.assertEqual(rtn, (None, 'Enter a value'))
rtn = IS_NOT_EMPTY()('')
self.assertEqual(rtn, ('', 'enter a value'))
self.assertEqual(rtn, ('', 'Enter a value'))
rtn = IS_NOT_EMPTY()(' ')
self.assertEqual(rtn, ('', 'enter a value'))
self.assertEqual(rtn, ('', 'Enter a value'))
rtn = IS_NOT_EMPTY()(' \n\t')
self.assertEqual(rtn, ('', 'enter a value'))
self.assertEqual(rtn, ('', 'Enter a value'))
rtn = IS_NOT_EMPTY()([])
self.assertEqual(rtn, ([], 'enter a value'))
self.assertEqual(rtn, ([], 'Enter a value'))
rtn = IS_NOT_EMPTY(empty_regex='def')('def')
self.assertEqual(rtn, ('', 'enter a value'))
self.assertEqual(rtn, ('', 'Enter a value'))
rtn = IS_NOT_EMPTY(empty_regex='de[fg]')('deg')
self.assertEqual(rtn, ('', 'enter a value'))
self.assertEqual(rtn, ('', 'Enter a value'))
rtn = IS_NOT_EMPTY(empty_regex='def')('abc')
self.assertEqual(rtn, ('abc', None))
@@ -621,7 +621,7 @@ class TestValidators(unittest.TestCase):
rtn = IS_SLUG(check=False)('a bc')
self.assertEqual(rtn, ('a-bc', None))
rtn = IS_SLUG(check=True)('a bc')
self.assertEqual(rtn, ('a bc', 'must be slug'))
self.assertEqual(rtn, ('a bc', 'Must be slug'))
def test_IS_STRONG(self):
rtn = IS_STRONG(es=True)('Abcd1234')
@@ -680,17 +680,17 @@ class TestValidators(unittest.TestCase):
rtn = IS_TIME()('5:30 pm')
self.assertEqual(rtn, (datetime.time(17, 30), None))
rtn = IS_TIME()('5:30 whatever')
self.assertEqual(rtn, ('5:30 whatever', 'enter time as hh:mm:ss (seconds, am, pm optional)'))
self.assertEqual(rtn, ('5:30 whatever', 'Enter time as hh:mm:ss (seconds, am, pm optional)'))
rtn = IS_TIME()('5:30 20')
self.assertEqual(rtn, ('5:30 20', 'enter time as hh:mm:ss (seconds, am, pm optional)'))
self.assertEqual(rtn, ('5:30 20', 'Enter time as hh:mm:ss (seconds, am, pm optional)'))
rtn = IS_TIME()('24:30')
self.assertEqual(rtn, ('24:30', 'enter time as hh:mm:ss (seconds, am, pm optional)'))
self.assertEqual(rtn, ('24:30', 'Enter time as hh:mm:ss (seconds, am, pm optional)'))
rtn = IS_TIME()('21:60')
self.assertEqual(rtn, ('21:60', 'enter time as hh:mm:ss (seconds, am, pm optional)'))
self.assertEqual(rtn, ('21:60', 'Enter time as hh:mm:ss (seconds, am, pm optional)'))
rtn = IS_TIME()('21:30::')
self.assertEqual(rtn, ('21:30::', 'enter time as hh:mm:ss (seconds, am, pm optional)'))
self.assertEqual(rtn, ('21:30::', 'Enter time as hh:mm:ss (seconds, am, pm optional)'))
rtn = IS_TIME()('')
self.assertEqual(rtn, ('', 'enter time as hh:mm:ss (seconds, am, pm optional)'))
self.assertEqual(rtn, ('', 'Enter time as hh:mm:ss (seconds, am, pm optional)'))
def test_IS_UPPER(self):
rtn = IS_UPPER()('abc')
@@ -716,7 +716,7 @@ class TestValidators(unittest.TestCase):
rtn = IS_JSON()('{"a": 100}')
self.assertEqual(rtn, ({u'a': 100}, None))
rtn = IS_JSON()('spam1234')
self.assertEqual(rtn, ('spam1234', 'invalid json'))
self.assertEqual(rtn, ('spam1234', 'Invalid json'))
if __name__ == '__main__':
+44 -1
View File
@@ -5,7 +5,10 @@
"""
import sys
import os
import unittest
if sys.version_info < (2, 7):
import unittest2 as unittest
else:
import unittest
import subprocess
import time
import signal
@@ -40,6 +43,7 @@ def fix_sys_path():
fix_sys_path()
from contrib.webclient import WebClient
from urllib2 import HTTPError
webserverprocess = None
@@ -59,6 +63,12 @@ def startwebserver():
for a in range(1,11):
time.sleep(1)
print a, '...'
try:
c = WebClient('http://127.0.0.1:8000')
c.get('/')
break
except:
continue
print ''
def terminate_process(pid):
@@ -93,6 +103,8 @@ class LiveTest(unittest.TestCase):
def tearDownClass(cls):
stopwebserver()
@unittest.skipIf("datastore" in os.getenv("DB", ""), "TODO: setup web test for app engine")
class TestWeb(LiveTest):
def testRegisterAndLogin(self):
client = WebClient('http://127.0.0.1:8000/welcome/default/')
@@ -140,6 +152,37 @@ class TestWeb(LiveTest):
assert('expires' in s.headers)
assert(s.headers['cache-control'].startswith('max-age'))
def testSoap(self):
# test soap server implementation
from gluon.contrib.pysimplesoap.client import SoapClient, SoapFault
url = 'http://127.0.0.1:8000/examples/soap_examples/call/soap?WSDL'
client = SoapClient(wsdl=url)
ret = client.SubIntegers(a=3, b=2)
# check that the value returned is ok
assert('SubResult' in ret)
assert(ret['SubResult'] == 1)
try:
ret = client.Division(a=3, b=0)
except SoapFault, sf:
# verify the exception value is ok
# assert(sf.faultstring == "float division by zero") # true only in 2.7
assert(sf.faultcode == "Server.ZeroDivisionError")
# store sent and received xml for low level test
xml_request = client.xml_request
xml_response = client.xml_response
# do a low level raw soap request (using
s = WebClient('http://127.0.0.1:8000/')
try:
s.post('examples/soap_examples/call/soap', data=xml_request, method="POST")
except HTTPError, e:
assert(e.msg=='INTERNAL SERVER ERROR')
# check internal server error returned (issue 153)
assert(s.status == 500)
assert(s.text == xml_response)
if __name__ == '__main__':
unittest.main()
+998 -747
View File
File diff suppressed because it is too large Load Diff
+61 -64
View File
@@ -1,15 +1,14 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
This file is part of the web2py Web Framework
Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu>
License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
Created by Vladyslav Kozlovskyy (Ukraine) <dbdevelop©gmail.com>
for Web2py project
| This file is part of the web2py Web Framework
| Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu>
| License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
| Created by Vladyslav Kozlovskyy (Ukraine) <dbdevelop©gmail.com>
| for Web2py project
Utilities and class for UTF8 strings managing
===========================================
----------------------------------------------
"""
import __builtin__
__all__ = ['Utf8']
@@ -30,18 +29,19 @@ repr_escape_tab2[ord('\'')] = u"\\'"
def sort_key(s):
""" Unicode Collation Algorithm (UCA) (http://www.unicode.org/reports/tr10/)
is used for utf-8 and unicode strings sorting and for utf-8 strings
comparison
"""Unicode Collation Algorithm (UCA) (http://www.unicode.org/reports/tr10/)
is used for utf-8 and unicode strings sorting and for utf-8 strings
comparison
NOTE: pyuca is a very memory cost module! It loads the whole
"allkey.txt" file (~2mb!) into the memory. But this
functionality is needed only when sort_key() is called as a
part of sort() function or when Utf8 strings are compared.
Note:
pyuca is a very memory cost module! It loads the whole
"allkey.txt" file (~2mb!) into the memory. But this
functionality is needed only when sort_key() is called as a
part of sort() function or when Utf8 strings are compared.
So, it is a lazy "sort_key" function which (ONLY ONCE, ON ITS
FIRST CALL) imports pyuca and replaces itself with a real
sort_key() function
So, it is a lazy "sort_key" function which (ONLY ONCE, ON ITS
FIRST CALL) imports pyuca and replaces itself with a real
sort_key() function
"""
global sort_key
try:
@@ -56,9 +56,8 @@ def sort_key(s):
def ord(char):
""" returns unicode id for utf8 or unicode *char* character
SUPPOSE that *char* is an utf-8 or unicode character only
"""Returns unicode id for utf8 or unicode *char* character
SUPPOSE that *char* is an utf-8 or unicode character only
"""
if isinstance(char, unicode):
return __builtin__.ord(char)
@@ -66,28 +65,29 @@ def ord(char):
def chr(code):
""" return utf8-character with *code* unicode id """
"""Returns utf8-character with *code* unicode id """
return Utf8(unichr(code))
def size(string):
""" return length of utf-8 string in bytes
NOTE! The length of correspondent utf-8
string is returned for unicode string
"""Returns length of utf-8 string in bytes
Note:
The length of correspondent utf-8 string is returned for unicode string
"""
return Utf8(string).__size__()
def truncate(string, length, dots='...'):
""" returns string of length < *length* or truncate
string with adding *dots* suffix to the string's end
"""Returns string of length < *length* or truncate string with adding
*dots* suffix to the string's end
args:
length (int): max length of string
dots (str or unicode): string suffix, when string is cutted
Args:
length (int): max length of string
dots (str or unicode): string suffix, when string is cutted
returns:
(utf8-str): original or cutted string
Returns:
(utf8-str): original or cutted string
"""
text = unicode(string, 'utf-8')
dots = unicode(dots, 'utf-8') if isinstance(dots, str) else dots
@@ -125,31 +125,32 @@ class Utf8(str):
def __repr__(self):
r''' # note that we use raw strings to avoid having to use double back slashes below
NOTE! This function is a clone of web2py:gluon.languages.utf_repl() function
NOTE! This function is a clone of web2py:gluon.languages.utf_repl() function::
utf8.__repr__() works same as str.repr() when processing ascii string
>>> repr(Utf8('abc')) == repr(Utf8("abc")) == repr('abc') == repr("abc") == "'abc'"
True
>>> repr(Utf8('a"b"c')) == repr('a"b"c') == '\'a"b"c\''
True
>>> repr(Utf8("a'b'c")) == repr("a'b'c") == '"a\'b\'c"'
True
>>> repr(Utf8('a\'b"c')) == repr('a\'b"c') == repr(Utf8("a'b\"c")) == repr("a'b\"c") == '\'a\\\'b"c\''
True
>>> repr(Utf8('a\r\nb')) == repr('a\r\nb') == "'a\\r\\nb'" # Test for \r, \n
True
utf8.__repr__() works same as str.repr() when processing ascii string
>>> repr(Utf8('abc')) == repr(Utf8("abc")) == repr('abc') == repr("abc") == "'abc'"
True
>>> repr(Utf8('a"b"c')) == repr('a"b"c') == '\'a"b"c\''
True
>>> repr(Utf8("a'b'c")) == repr("a'b'c") == '"a\'b\'c"'
True
>>> repr(Utf8('a\'b"c')) == repr('a\'b"c') == repr(Utf8("a'b\"c")) == repr("a'b\"c") == '\'a\\\'b"c\''
True
>>> repr(Utf8('a\r\nb')) == repr('a\r\nb') == "'a\\r\\nb'" # Test for \r, \n
True
Unlike str.repr(), Utf8.__repr__() remains utf8 content when processing utf8 string
>>> repr(Utf8('中文字')) == repr(Utf8("中文字")) == "'中文字'" != repr('中文字')
True
>>> repr(Utf8('中"文"字')) == "'中\"文\"字'" != repr('中"文"字')
True
>>> repr(Utf8("中'文'字")) == '"中\'文\'字"' != repr("中'文'字")
True
>>> repr(Utf8('中\'文"字')) == repr(Utf8("中'文\"字")) == '\'中\\\'文"字\'' != repr('中\'文"字') == repr("中'文\"字")
True
>>> repr(Utf8('中\r\n文')) == "'中\\r\\n文'" != repr('中\r\n文') # Test for \r, \n
True
Unlike str.repr(), Utf8.__repr__() remains utf8 content when processing utf8 string::
>>> repr(Utf8('中文字')) == repr(Utf8("中文字")) == "'中文字'" != repr('中文字')
True
>>> repr(Utf8('中"文"字')) == "'中\"文\"字'" != repr('中"文"字')
True
>>> repr(Utf8("中'文'字")) == '"中\'文\'字"' != repr("中'文'字")
True
>>> repr(Utf8('中\'文"字')) == repr(Utf8("中'文\"字")) == '\'中\\\'文"字\'' != repr('中\'文"字') == repr("中'文\"字")
True
>>> repr(Utf8('中\r\n文')) == "'中\\r\\n文'" != repr('中\r\n文') # Test for \r, \n
True
'''
if str.find(self, "'") >= 0 and str.find(self, '"') < 0: # only single quote exists
return '"' + unicode(self, 'utf-8').translate(repr_escape_tab).encode('utf-8') + '"'
@@ -578,11 +579,9 @@ if __name__ == '__main__':
7
>>> a=Utf8('а б ц д е а б ц д е а\\tб ц д е')
>>> a.split()
['а', 'б', 'ц', 'д', 'е', 'а', 'б', 'ц', 'д',
'е', 'а', 'б', 'ц', 'д', 'е']
['а', 'б', 'ц', 'д', 'е', 'а', 'б', 'ц', 'д', 'е', 'а', 'б', 'ц', 'д', 'е']
>>> a.rsplit()
['а', 'б', 'ц', 'д', 'е', 'а', 'б', 'ц', 'д',
'е', 'а', 'б', 'ц', 'д', 'е']
['а', 'б', 'ц', 'д', 'е', 'а', 'б', 'ц', 'д', 'е', 'а', 'б', 'ц', 'д', 'е']
>>> a.expandtabs().split('б')
['а ', ' ц д е а ', ' ц д е а ', ' ц д е']
>>> a.expandtabs().rsplit('б')
@@ -630,8 +629,7 @@ if __name__ == '__main__':
1
>>> s.count('Є', 0, 5)
0
>>> Utf8(
"Parameters: '%(проба)s', %(probe)04d, %(проба2)s") % { u"проба": s,
>>> Utf8("Parameters: '%(проба)s', %(probe)04d, %(проба2)s") % { u"проба": s,
... "not used": "???", "probe": 2, "проба2": u"ПРоба Probe" }
"Parameters: 'ПРоба Є PRobe', 0002, ПРоба Probe"
>>> a=Utf8(u"Параметр: (%s)-(%s)-[%s]")
@@ -694,8 +692,7 @@ if __name__ == '__main__':
аАбБвВгГґҐдДеЕєЄжЖзЗиИіІїЇйЙкКлЛмМнНоОпПрРсСтТуУфФхХцЦчЧшШщЩьЬюЮяЯ
>>> Utf8().join(sorted(c.decode(), key=sort_key)) # convert to unicode for better performance
'аАбБвВгГґҐдДеЕєЄжЖзЗиИіІїЇйЙкКлЛмМнНоОпПрРсСтТуУфФхХцЦчЧшШщЩьЬюЮяЯ'
>>> for result in sorted(
["Іа", "Астро", u"гала", Utf8("Гоша"), "Єва", "шовк", "аякс", "Їжа",
>>> for result in sorted(["Іа", "Астро", u"гала", Utf8("Гоша"), "Єва", "шовк", "аякс", "Їжа",
... "ґанок", Utf8("Дар'я"), "білінг", "веб", u"Жужа", "проба", u"тест",
... "абетка", "яблуко", "Юляся", "Київ", "лимонад", "ложка", "Матриця",
... ], key=sort_key):
@@ -722,6 +719,7 @@ if __name__ == '__main__':
шовк <type 'str'>
Юляся <type 'str'>
яблуко <type 'str'>
>>> a=Utf8("中文字")
>>> L=list(a)
>>> L
@@ -734,8 +732,7 @@ if __name__ == '__main__':
>>> a="中文字" # standard str type
>>> L=list(a)
>>> L
['\\xe4', '\\xb8', '\\xad', '\\xe6', '\\x96', '\\x87',
'\\xe5', '\\xad', '\\x97']
['\\xe4', '\\xb8', '\\xad', '\\xe6', '\\x96', '\\x87', '\\xe5', '\\xad', '\\x97']
>>> from string import maketrans
>>> str_tab=maketrans('PRobe','12345')
>>> unicode_tab={ord(u'П'):ord(u'Ж'),
+30 -15
View File
@@ -2,17 +2,19 @@
# -*- coding: utf-8 -*-
"""
This file is part of the web2py Web Framework
Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu>
License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
| This file is part of the web2py Web Framework
| Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu>
| License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
This file specifically includes utilities for security.
--------------------------------------------------------
"""
import threading
import struct
import uuid
import random
import inspect
import time
import os
import re
@@ -65,7 +67,7 @@ def AES_new(key, IV=None):
def compare(a, b):
""" compares two strings and not vulnerable to timing attacks """
""" Compares two strings and not vulnerable to timing attacks """
if len(a) != len(b):
return False
result = 0
@@ -75,7 +77,7 @@ def compare(a, b):
def md5_hash(text):
""" Generate a md5 hash with the given text """
""" Generates a md5 hash with the given text """
return md5(text).hexdigest()
def simple_hash(text, key='', salt='', digest_alg='md5'):
@@ -131,6 +133,16 @@ DIGEST_ALG_BY_SIZE = {
512 / 4: 'sha512',
}
def get_callable_argspec(fn):
if inspect.isfunction(fn) or inspect.ismethod(fn):
inspectable = fn
elif inspect.isclass(fn):
inspectable = fn.__init__
elif hasattr(fn, '__call__'):
inspectable = fn.__call__
else:
inspectable = fn
return inspect.getargspec(inspectable)
def pad(s, n=32, padchar=' '):
return s + (32 - len(s) % 32) * padchar
@@ -168,7 +180,7 @@ def secure_loads(data, encryption_key, hash_key=None, compression_level=None):
if compression_level:
data = zlib.decompress(data)
return pickle.loads(data)
except (TypeError, pickle.UnpicklingError):
except Exception, e:
return None
### compute constant CTOKENS
@@ -177,7 +189,7 @@ def secure_loads(data, encryption_key, hash_key=None, compression_level=None):
def initialize_urandom():
"""
This function and the web2py_uuid follow from the following discussion:
http://groups.google.com/group/web2py-developers/browse_thread/thread/7fd5789a7da3f09
`http://groups.google.com/group/web2py-developers/browse_thread/thread/7fd5789a7da3f09`
At startup web2py compute a unique ID that identifies the machine by adding
uuid.getnode() + int(time.time() * 1e3)
@@ -225,7 +237,7 @@ UNPACKED_CTOKENS, HAVE_URANDOM = initialize_urandom()
def fast_urandom16(urandom=[], locker=threading.RLock()):
"""
this is 4x faster than calling os.urandom(16) and prevents
This is 4x faster than calling os.urandom(16) and prevents
the "too many files open" issue with concurrent access to os.urandom()
"""
try:
@@ -243,7 +255,7 @@ def fast_urandom16(urandom=[], locker=threading.RLock()):
def web2py_uuid(ctokens=UNPACKED_CTOKENS):
"""
This function follows from the following discussion:
http://groups.google.com/group/web2py-developers/browse_thread/thread/7fd5789a7da3f09
`http://groups.google.com/group/web2py-developers/browse_thread/thread/7fd5789a7da3f09`
It works like uuid.uuid4 except that tries to use os.urandom() if possible
and it XORs the output with the tokens uniquely associated with this machine.
@@ -263,12 +275,15 @@ REGEX_IPv4 = re.compile('(\d+)\.(\d+)\.(\d+)\.(\d+)')
def is_valid_ip_address(address):
"""
>>> is_valid_ip_address('127.0')
False
>>> is_valid_ip_address('127.0.0.1')
True
>>> is_valid_ip_address('2001:660::1')
True
Examples:
Better than a thousand words::
>>> is_valid_ip_address('127.0')
False
>>> is_valid_ip_address('127.0.0.1')
True
>>> is_valid_ip_address('2001:660::1')
True
"""
# deal with special cases
if address.lower() in ('127.0.0.1', 'localhost', '::1', '::ffff:127.0.0.1'):
+850 -775
View File
File diff suppressed because it is too large Load Diff
+44 -21
View File
@@ -2,11 +2,12 @@
# -*- coding: utf-8 -*-
"""
This file is part of the web2py Web Framework
Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu>
License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
| This file is part of the web2py Web Framework
| Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu>
| License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
The widget is called from web2py.
The widget is called from web2py
----------------------------------
"""
import datetime
@@ -140,7 +141,7 @@ class web2pyDialog(object):
bg_color = 'white'
root.withdraw()
self.root = Tkinter.Toplevel(root, bg=bg_color)
self.root.resizable(0,0)
self.root.title(ProgramName)
@@ -224,8 +225,8 @@ class web2pyDialog(object):
text=str(ProgramVersion + "\n" + ProgramAuthor),
font=('Helvetica', 11), justify=Tkinter.CENTER,
foreground='#195866', background=bg_color,
height=3).pack( side='top',
fill='both',
height=3).pack( side='top',
fill='both',
expand='yes')
self.bannerarea.after(1000, self.update_canvas)
@@ -245,7 +246,7 @@ class web2pyDialog(object):
[('0.0.0.0', 'Public')]
for ip, legend in ips:
self.ips[ip] = Tkinter.Radiobutton(
self.root, bg=bg_color, highlightthickness=0,
self.root, bg=bg_color, highlightthickness=0,
selectcolor='light grey', width=30,
anchor=Tkinter.W, text='%s (%s)' % (legend, ip),
justify=Tkinter.LEFT,
@@ -379,7 +380,7 @@ class web2pyDialog(object):
t.start()
def checkTaskBar(self):
""" Check taskbar status """
""" Checks taskbar status """
if self.tb.status:
if self.tb.status[0] == self.tb.EnumStatus.QUIT:
@@ -401,7 +402,7 @@ class web2pyDialog(object):
self.root.after(1000, self.checkTaskBar)
def update(self, text):
""" Update app text """
""" Updates app text """
try:
self.text.configure(state='normal')
@@ -411,7 +412,7 @@ class web2pyDialog(object):
pass # ## this should only happen in case app is destroyed
def connect_pages(self):
""" Connect pages """
""" Connects pages """
#reset the menu
available_apps = [arq for arq in os.listdir('applications/')
if os.path.exists(
@@ -423,7 +424,7 @@ class web2pyDialog(object):
label=url, command=lambda u=url: start_browser(u))
def quit(self, justHide=False):
""" Finish the program execution """
""" Finishes the program execution """
if justHide:
self.root.withdraw()
else:
@@ -450,13 +451,13 @@ class web2pyDialog(object):
sys.exit(0)
def error(self, message):
""" Show error message """
""" Shows error message """
import tkMessageBox
tkMessageBox.showerror('web2py start server', message)
def start(self):
""" Start web2py server """
""" Starts web2py server """
password = self.password.get()
@@ -536,7 +537,7 @@ class web2pyDialog(object):
return False
def stop(self):
""" Stop web2py server """
""" Stops web2py server """
self.button_start.configure(state='normal')
self.button_stop.configure(state='disabled')
@@ -549,7 +550,7 @@ class web2pyDialog(object):
self.tb.SetServerStopped()
def update_canvas(self):
""" Update canvas """
""" Updates canvas """
try:
t1 = os.path.getsize('httpserver.log')
@@ -617,6 +618,12 @@ def console():
type='int',
help='port of server (8000)')
parser.add_option('-G',
'--GAE',
default=None,
dest='gae',
help="'-G configure' will create app.yaml and gaehandler.py")
msg = ('password to be used for administration '
'(use -a "<recycle>" to reuse the last password))')
parser.add_option('-a',
@@ -922,6 +929,20 @@ def console():
global_settings.cmd_options = options
global_settings.cmd_args = args
if options.gae:
if not os.path.exists('app.yaml'):
name = raw_input("Your GAE app name: ")
content = open(os.path.join('examples','app.example.yaml'),'rb').read()
open('app.yaml','wb').write(content.replace("yourappname",name))
else:
print "app.yaml alreday exists in the web2py folder"
if not os.path.exists('gaehandler.py'):
content = open(os.path.join('handlers','gaehandler.py'),'rb').read()
open('gaehandler.py','wb').write(content)
else:
print "gaehandler.py alreday exists in the web2py folder"
sys.exit(0)
try:
options.ips = list(set( # no duplicates
[addrinfo[4][0] for addrinfo in getipaddrinfo(socket.getfqdn())
@@ -1054,7 +1075,7 @@ def start_schedulers(options):
def start(cron=True):
""" Start server """
""" Starts server """
# ## get command line arguments
@@ -1098,6 +1119,8 @@ def start(cron=True):
# ## if -S start interactive shell (also no cron)
if options.shell:
if options.folder:
os.chdir(options.folder)
if not options.args is None:
sys.argv[:] = options.args
run(options.shell, plain=options.plain, bpython=options.bpython,
@@ -1222,12 +1245,12 @@ end tell
url = get_url(ip, proto=proto, port=port)
if not options.nobanner:
print 'please visit:'
print '\t', url
message = '\nplease visit:\n\t%s\n' % url
if sys.platform.startswith('win'):
print 'use "taskkill /f /pid %i" to shutdown the web2py server' % os.getpid()
message += 'use "taskkill /f /pid %i" to shutdown the web2py server\n\n' % os.getpid()
else:
print 'use "kill -SIGTERM %i" to shutdown the web2py server' % os.getpid()
message += 'use "kill -SIGTERM %i" to shutdown the web2py server\n\n' % os.getpid()
print message
# enhance linecache.getline (used by debugger) to look at the source file
# if the line was not found (under py2exe & when file was modified)
+3 -3
View File
@@ -2,9 +2,9 @@
# -*- coding: utf-8 -*-
"""
This file is part of the web2py Web Framework
Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu>
License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
| This file is part of the web2py Web Framework
| Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu>
| License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
"""
from SimpleXMLRPCServer import SimpleXMLRPCDispatcher