Merge branch 'master' of git://github.com/web2py/web2py
Conflicts: applications/admin/languages/pt.py gluon/dal.py
This commit is contained in:
+1
-1
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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']
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'))
|
||||
|
||||
|
||||
@@ -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},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
"""
|
||||
Written by Michele Comitini <mcm@glisco.it>
|
||||
License: GPL v3
|
||||
License: LGPL v3
|
||||
|
||||
Adds support for x509 authentication.
|
||||
|
||||
|
||||
@@ -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={},
|
||||
|
||||
@@ -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,6 +1,6 @@
|
||||
"""
|
||||
Created by Massimo Di Pierro
|
||||
Licese BSD
|
||||
License BSD
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
|
||||
@@ -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):
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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
|
||||
@@ -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']))
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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())
|
||||
|
||||
|
||||
@@ -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,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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
@@ -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
File diff suppressed because it is too large
Load Diff
+9
-8
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
File diff suppressed because it is too large
Load Diff
+26
-3
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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:
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
File diff suppressed because it is too large
Load Diff
+5
-3
@@ -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
@@ -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
@@ -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
File diff suppressed because it is too large
Load Diff
+101
-74
@@ -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
@@ -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
@@ -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
@@ -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 *
|
||||
|
||||
@@ -25,6 +25,7 @@ exclude_lines =
|
||||
|
||||
ignore_errors = True
|
||||
omit = gluon/contrib/*
|
||||
gluon/tests/*
|
||||
|
||||
[html]
|
||||
directory = coverage_html_report
|
||||
|
||||
+115
-84
@@ -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
@@ -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"><></a>')
|
||||
self.assertEqual(
|
||||
A('<>', _a='1', _b='2').xml(),
|
||||
'<a a="1" b="2"><></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><></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="<def?asd>" data-cde="standard" data-w2p_disable_with="default"><></a>')
|
||||
self.assertEqual(A('<>', data=dict(abc='<def?asd>', cde='standard'), _a='1', _b='2').xml(),'<a a="1" b="2" data-abc="<def?asd>" data-cde="standard"><></a>')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
+41
-41
@@ -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
@@ -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
@@ -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):
|
||||
'''
|
||||
|
||||
@@ -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
@@ -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
@@ -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
File diff suppressed because it is too large
Load Diff
+61
-64
@@ -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
@@ -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
File diff suppressed because it is too large
Load Diff
+44
-21
@@ -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
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user