From 8fb1a954ba189bb646b8db7a1e0184507ea55958 Mon Sep 17 00:00:00 2001 From: Michele Comitini Date: Tue, 30 Jul 2013 23:24:01 +0200 Subject: [PATCH 1/9] oauth1 timeout. pbkdf2_ctypes repository reference in docstring --- gluon/contrib/login_methods/oauth10a_account.py | 5 +++-- gluon/contrib/pbkdf2_ctypes.py | 4 +++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/gluon/contrib/login_methods/oauth10a_account.py b/gluon/contrib/login_methods/oauth10a_account.py index f6cfeb0e..e25014da 100644 --- a/gluon/contrib/login_methods/oauth10a_account.py +++ b/gluon/contrib/login_methods/oauth10a_account.py @@ -109,7 +109,7 @@ class OAuthAccount(object): self.session.access_token = None return None - def __init__(self, g, client_id, client_secret, auth_url, token_url, access_token_url): + def __init__(self, g, client_id, client_secret, auth_url, token_url, access_token_url, socket_timeout=60): self.globals = g self.client_id = client_id self.client_secret = client_secret @@ -119,6 +119,7 @@ class OAuthAccount(object): self.auth_url = auth_url self.token_url = token_url self.access_token_url = access_token_url + self.socket_timeout = socket_timeout # consumer init self.consumer = oauth.Consumer(self.client_id, self.client_secret) @@ -154,7 +155,7 @@ class OAuthAccount(object): if not self.accessToken(): # setup the client - client = oauth.Client(self.consumer, None) + client = oauth.Client(self.consumer, None, timeout=self.socket_timeout) # Get a request token. # oauth_callback *is REQUIRED* for OAuth1.0a # putting it in the body seems to work. diff --git a/gluon/contrib/pbkdf2_ctypes.py b/gluon/contrib/pbkdf2_ctypes.py index 3627abed..cb220f6e 100644 --- a/gluon/contrib/pbkdf2_ctypes.py +++ b/gluon/contrib/pbkdf2_ctypes.py @@ -9,6 +9,8 @@ Note: This module is intended as a plugin replacement of pbkdf2.py by Armin Ronacher. + Git repository: + $ git clone https://github.com/michele-comitini/pbkdf2_ctypes.git :copyright: Copyright (c) 2013: Michele Comitini :license: LGPLv3 @@ -42,7 +44,7 @@ try: # check that we have proper OpenSSL on the system. hashlib.sha224: crypto.EVP_sha224, hashlib.sha384: crypto.EVP_sha384, hashlib.sha512: crypto.EVP_sha512} -except OSError, AttributeError: +except (OSError, AttributeError), e: raise ImportError('Cannot find a compatible OpenSSL installation ' 'on your system') From 0e4aa6a39599eb04c2201cc3bfa18762ec6eb2c8 Mon Sep 17 00:00:00 2001 From: niphlod Date: Wed, 31 Jul 2013 21:48:06 +0200 Subject: [PATCH 2/9] added roadmap in the community docs section --- .../private/content/en/default/documentation/community.markmin | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/applications/examples/private/content/en/default/documentation/community.markmin b/applications/examples/private/content/en/default/documentation/community.markmin index 6a9f2e46..43551da1 100644 --- a/applications/examples/private/content/en/default/documentation/community.markmin +++ b/applications/examples/private/content/en/default/documentation/community.markmin @@ -1,6 +1,7 @@ ### Community Documentation -- [[web2pybrasil http://www.web2pybrasil.com.br popup]] +- [[Roadmap https://trello.com/b/d3aqBbBl/web2py-roadmap]] +- [[web2pybrasil http://www.web2pybrasil.com.br popup]] - Apostila em português [[Download http://dl.dropbox.com/u/830444/apostila_web2py_basico.pdf popup]] e [[Online http://web2pybrasil.appspot.com/init/plugin_wiki/page/curso-web2py-000 popup]] - [[Sitio en español http://www.web2py.com.ar popup]] - [[Documentación en español http://www.web2py.com.ar/examples/default/docs popup]] From a2566ef7abd65a9c44da3ff7948e015d2f221e4e Mon Sep 17 00:00:00 2001 From: mdipierro Date: Thu, 1 Aug 2013 02:00:08 -0500 Subject: [PATCH 3/9] call to build_parsemap() on __init__ instead of at every select(), thanks Niphlod --- VERSION | 2 +- gluon/dal.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/VERSION b/VERSION index 232414f3..d5ffe6ab 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.6.0-development+timestamp.2013.07.31.11.44.33 +Version 2.6.0-development+timestamp.2013.08.01.01.59.18 diff --git a/gluon/dal.py b/gluon/dal.py index 29714a33..9e6a0ab3 100644 --- a/gluon/dal.py +++ b/gluon/dal.py @@ -2058,7 +2058,6 @@ class BaseAdapter(ConnectionPool): def parse(self, rows, fields, colnames, blob_decode=True, cacheable = False): - self.build_parsemap() db = self.db virtualtables = [] new_rows = [] @@ -7410,6 +7409,7 @@ class DAL(object): types = ADAPTERS[self._dbname].types # copy so multiple DAL() possible self._adapter.types = copy.copy(types) + self._adapter.build_parsemap() if bigint_id: if 'big-id' in types and 'reference' in types: self._adapter.types['id'] = types['big-id'] From df4d8a523860eaa60547539188faf4fed1d7cf74 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Thu, 1 Aug 2013 02:49:27 -0500 Subject: [PATCH 4/9] no more removal of ORDER BY in MSSQL --- VERSION | 2 +- gluon/dal.py | 11 +++-------- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/VERSION b/VERSION index d5ffe6ab..666e55b6 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.6.0-development+timestamp.2013.08.01.01.59.18 +Version 2.6.0-development+timestamp.2013.08.01.02.48.50 diff --git a/gluon/dal.py b/gluon/dal.py index 9e6a0ab3..677d1139 100644 --- a/gluon/dal.py +++ b/gluon/dal.py @@ -1676,10 +1676,9 @@ class BaseAdapter(ConnectionPool): sql_o += ' ORDER BY %s' % self.RANDOM() else: sql_o += ' ORDER BY %s' % self.expand(orderby) - if limitby: - if orderby_on_limitby and not orderby and tablenames: - sql_o += ' ORDER BY %s' % ', '.join(['%s.%s'%(t,x) for t in tablenames for x in (hasattr(self.db[t],'_primarykey') and self.db[t]._primarykey or [self.db[t]._id.name])]) - # oracle does not support limitby + if (limitby and not groupby and tablenames and orderby_on_limitby): + sql_o += ' ORDER BY %s' % ', '.join(['%s.%s'%(t,x) for t in tablenames for x in (hasattr(self.db[t],'_primarykey') and self.db[t]._primarykey or [self.db[t]._id.name])]) + # oracle does not support limitby sql = self.select_limitby(sql_s, sql_f, sql_t, sql_w, sql_o, limitby) if for_update and self.can_select_for_update is True: sql = sql.rstrip(';') + ' FOR UPDATE;' @@ -3173,10 +3172,6 @@ class MSSQLAdapter(BaseAdapter): if limitby: (lmin, lmax) = limitby sql_s += ' TOP %i' % lmax - if 'GROUP BY' in sql_o: - orderfound = sql_o.find('ORDER BY ') - if orderfound >= 0: - sql_o = sql_o[:orderfound] return 'SELECT %s %s FROM %s%s%s;' % (sql_s, sql_f, sql_t, sql_w, sql_o) TRUE = 1 From eb2c0e6ca158d286eada3e99ccbfef39c0e5f930 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Thu, 1 Aug 2013 08:23:24 -0500 Subject: [PATCH 5/9] added better handling of missing library, thanks Michele --- VERSION | 2 +- gluon/contrib/pbkdf2_ctypes.py | 95 ++++++++++++++++++++++++++-------- 2 files changed, 73 insertions(+), 24 deletions(-) diff --git a/VERSION b/VERSION index 666e55b6..ca9652d7 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.6.0-development+timestamp.2013.08.01.02.48.50 +Version 2.6.0-development+timestamp.2013.08.01.08.22.32 diff --git a/gluon/contrib/pbkdf2_ctypes.py b/gluon/contrib/pbkdf2_ctypes.py index cb220f6e..1e7d8a9d 100644 --- a/gluon/contrib/pbkdf2_ctypes.py +++ b/gluon/contrib/pbkdf2_ctypes.py @@ -23,7 +23,62 @@ import hashlib import platform import os.path -try: # check that we have proper OpenSSL on the system. +def commoncrypto_hashlib_to_crypto_map_get(hashfunc): + hashlib_to_crypto_map = {hashlib.sha1: 1, + hashlib.sha224: 2, + hashlib.sha256: 3, + hashlib.sha384: 4, + hashlib.sha512: 5} + crypto_hashfunc = hashlib_to_crypto_map.get(hashfunc) + if crypto_hashfunc is None: + raise ValueError('Unkwnown digest %s' % hashfunc) + return crypto_hashfunc + +def commoncrypto_pbkdf2(c_pass, c_passlen, + c_salt, c_saltlen, + c_iter, digest, c_keylen, c_buff): + """Common Crypto compatibile wrapper + """ + c_hashfunc = ctypes.c_int(commoncrypto_hashlib_to_crypto_map_get(digest)) + + return 1 - crypto.CCKeyDerivationPBKDF(2, # hardcoded 2-> PBKDF2 + c_pass, c_passlen, + c_salt, c_saltlen, + c_hashfunc, + c_iter, + c_buff, + c_keylen) + + +def openssl_hashlib_to_crypto_map_get(hashfunc): + hashlib_to_crypto_map = {hashlib.md5: crypto.EVP_md5, + hashlib.sha1: crypto.EVP_sha1, + hashlib.sha256: crypto.EVP_sha256, + hashlib.sha224: crypto.EVP_sha224, + hashlib.sha384: crypto.EVP_sha384, + hashlib.sha512: crypto.EVP_sha512} + crypto_hashfunc = hashlib_to_crypto_map.get(hashfunc) + if crypto_hashfunc is None: + raise ValueError('Unkwnown digest %s' % hashfunc) + crypto_hashfunc.restype = ctypes.c_void_p + return crypto_hashfunc() + + +def openssl_pbkdf2(c_pass, c_passlen, + c_salt, c_saltlen, + c_iter, digest, c_keylen, c_buff): + """OpenSSL compatibile wrapper + """ + c_hashfunc = ctypes.c_void_p(openssl_hashlib_to_crypto_map_get(digest)) + + return crypto.PKCS5_PBKDF2_HMAC(c_pass, c_passlen, + c_salt, c_saltlen, + c_iter, + c_hashfunc, + c_keylen, + c_buff) + +try: # check that we have proper OpenSSL or Common Crypto on the system. system = platform.system() if system == 'Windows': if platform.architecture()[0] == '64bit': @@ -32,18 +87,16 @@ try: # check that we have proper OpenSSL on the system. else: crypto = ctypes.CDLL(os.path.basename( ctypes.util.find_library('libeay32'))) + _pbkdf2_hmac = openssl_pbkdf2 + crypto.PKCS5_PBKDF2_HMAC # test compatibility + elif system == 'Darwin': # think different(TM)! i.e. break things! + raise ImportError('Not yet available on OS X') else: crypto = ctypes.CDLL(os.path.basename( ctypes.util.find_library('crypto'))) + _pbkdf2_hmac = openssl_pbkdf2 + crypto.PKCS5_PBKDF2_HMAC # test compatibility - PKCS5_PBKDF2_HMAC = crypto.PKCS5_PBKDF2_HMAC - - hashlib_to_crypto_map = {hashlib.md5: crypto.EVP_md5, - hashlib.sha1: crypto.EVP_sha1, - hashlib.sha256: crypto.EVP_sha256, - hashlib.sha224: crypto.EVP_sha224, - hashlib.sha384: crypto.EVP_sha384, - hashlib.sha512: crypto.EVP_sha512} except (OSError, AttributeError), e: raise ImportError('Cannot find a compatible OpenSSL installation ' 'on your system') @@ -56,20 +109,11 @@ def pkcs5_pbkdf2_hmac(data, salt, iterations=1000, keylen=24, hashfunc=None): c_saltlen = ctypes.c_int(len(salt)) c_iter = ctypes.c_int(iterations) c_keylen = ctypes.c_int(keylen) - if hashfunc: - crypto_hashfunc = hashlib_to_crypto_map.get(hashfunc) - crypto_hashfunc.restype = ctypes.c_void_p - if crypto_hashfunc is None: - raise ValueError('Unknown digest' + str(hashfunc)) - c_digest = ctypes.c_void_p(crypto_hashfunc()) - else: - crypto.EVP_sha1.restype = ctypes.c_void_p - c_digest = ctypes.c_void_p(crypto.EVP_sha1()) c_buff = ctypes.create_string_buffer('\000' * keylen) - err = PKCS5_PBKDF2_HMAC(c_pass, c_passlen, + err = _pbkdf2_hmac(c_pass, c_passlen, c_salt, c_saltlen, c_iter, - c_digest, + hashfunc, c_keylen, c_buff) @@ -87,7 +131,12 @@ def pbkdf2_bin(data, salt, iterations=1000, keylen=24, hashfunc=None): return pkcs5_pbkdf2_hmac(data, salt, iterations, keylen, hashfunc) if __name__ == '__main__': - crypto.SSLeay_version.restype = ctypes.c_char_p - print crypto.SSLeay_version(0) - for h in hashlib_to_crypto_map: + try: + crypto.SSLeay_version.restype = ctypes.c_char_p + print crypto.SSLeay_version(0) + except: + print "Not using OpenSSL" + + for h in [hashlib.sha1, hashlib.sha224, hashlib.sha256, + hashlib.sha384, hashlib.sha512]: pkcs5_pbkdf2_hmac('secret' * 11, 'salt', hashfunc=h) From 275cc3bfb44655aa0da5200ea18015838e3bcada Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=B6k=C3=A7en=20Eraslan?= Date: Thu, 1 Aug 2013 17:50:01 +0300 Subject: [PATCH 6/9] Fix form validators in admin application. --- applications/admin/views/default.mobile/design.html | 4 ++-- applications/admin/views/default.mobile/plugin.html | 4 ++-- applications/admin/views/default/design.html | 4 ++-- applications/admin/views/default/plugin.html | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/applications/admin/views/default.mobile/design.html b/applications/admin/views/default.mobile/design.html index 6ad9e28b..bcdd3c37 100644 --- a/applications/admin/views/default.mobile/design.html +++ b/applications/admin/views/default.mobile/design.html @@ -34,14 +34,14 @@ def editlanguagefile(path,file): def file_upload_form(location): form=FORM(T("upload file:")," ", INPUT(_type="file",_name="file")," ",T("and rename it:")," ", - INPUT(_type="text",_name="filename",requires=IS_NOT_EMPTY), + INPUT(_type="text",_name="filename",requires=IS_NOT_EMPTY()), INPUT(_type="hidden",_name="location",_value=location), INPUT(_type="hidden",_name="sender",_value=URL('design',args=app)), INPUT(_type="submit",_value=T("upload")),_action=URL('upload_file')) return form def file_create_form(location): form=FORM(T("create file with filename:")," ", - INPUT(_type="text",_name="filename",requires=IS_NOT_EMPTY), + INPUT(_type="text",_name="filename",requires=IS_NOT_EMPTY()), INPUT(_type="hidden",_name="location",_value=location), INPUT(_type="hidden",_name="sender",_value=URL('design',args=app))) return form diff --git a/applications/admin/views/default.mobile/plugin.html b/applications/admin/views/default.mobile/plugin.html index f13202ce..d42ec974 100644 --- a/applications/admin/views/default.mobile/plugin.html +++ b/applications/admin/views/default.mobile/plugin.html @@ -35,14 +35,14 @@ def editlanguagefile(path,file): def file_upload_form(location): form=FORM(T("upload file:")," ", INPUT(_type="file",_name="file")," ",T("and rename it:")," ", - INPUT(_type="text",_name="filename",requires=IS_NOT_EMPTY), + INPUT(_type="text",_name="filename",requires=IS_NOT_EMPTY()), INPUT(_type="hidden",_name="location",_value=location), INPUT(_type="hidden",_name="sender",_value=URL('design/'+app)), INPUT(_type="submit",_value=T("submit")),_action=URL('upload_file')) return form def file_create_form(location): form=FORM(T("create file with filename:")," ", - INPUT(_type="text",_name="filename",requires=IS_NOT_EMPTY), + INPUT(_type="text",_name="filename",requires=IS_NOT_EMPTY()), INPUT(_type="hidden",_name="location",_value=location), INPUT(_type="hidden",_name="sender",_value=URL('design/'+app)), INPUT(_type="submit",_value=T("submit")),_action=URL('create_file')) diff --git a/applications/admin/views/default/design.html b/applications/admin/views/default/design.html index 205e65a8..c9ed67a2 100644 --- a/applications/admin/views/default/design.html +++ b/applications/admin/views/default/design.html @@ -25,7 +25,7 @@ def file_upload_form(location, anchor=None): LABEL(T("upload file:")), INPUT(_type="file",_name="file"), LABEL(T("and rename it:")), - INPUT(_type="text",_name="filename",requires=IS_NOT_EMPTY,_class=""), + INPUT(_type="text",_name="filename",requires=IS_NOT_EMPTY(),_class=""), DIV(TAG['BUTTON'](T("Upload"),_type="submit",_class="btn"),_class="controls"), INPUT(_type="hidden",_name="location",_value=location), INPUT(_type="hidden",_name="token",_value=session.token), @@ -36,7 +36,7 @@ def file_upload_form(location, anchor=None): def file_create_form(location, anchor=None, helptext=""): form=FORM( LABEL(T("create file with filename:")), - INPUT(_type="text",_name="filename",requires=IS_NOT_EMPTY,_class=''), + INPUT(_type="text",_name="filename",requires=IS_NOT_EMPTY(),_class=''), TAG['SMALL'](helptext,_class="help-block"), DIV(TAG['BUTTON'](T("Create"),_type="submit",_class="btn"),_class="controls"), INPUT(_type="hidden",_name="location",_value=location), diff --git a/applications/admin/views/default/plugin.html b/applications/admin/views/default/plugin.html index cc4d2be0..0fd8ea1a 100644 --- a/applications/admin/views/default/plugin.html +++ b/applications/admin/views/default/plugin.html @@ -20,7 +20,7 @@ def editlanguagefile(path,file): def file_upload_form(location): form=FORM(T("upload file:")," ", INPUT(_type="file",_name="file")," ",T("and rename it:")," ", - INPUT(_type="text",_name="filename",requires=IS_NOT_EMPTY), + INPUT(_type="text",_name="filename",requires=IS_NOT_EMPTY()), INPUT(_type="hidden",_name="location",_value=location), INPUT(_type="hidden",_name="token",_value=session.token), INPUT(_type="hidden",_name="sender",_value=URL('design/'+app)), @@ -28,7 +28,7 @@ def file_upload_form(location): return form def file_create_form(location): form=FORM(T("create file with filename:")," ", - INPUT(_type="text",_name="filename",requires=IS_NOT_EMPTY), + INPUT(_type="text",_name="filename",requires=IS_NOT_EMPTY()), INPUT(_type="hidden",_name="location",_value=location), INPUT(_type="hidden",_name="token",_value=session.token), INPUT(_type="hidden",_name="sender",_value=URL('design/'+app)), From 99d73e03a5a18b2c4559d455a4b9fe5a3ce2cfec Mon Sep 17 00:00:00 2001 From: Ricardo Pedroso Date: Fri, 2 Aug 2013 15:54:48 +0100 Subject: [PATCH 7/9] fix typo in dal.py --- gluon/dal.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gluon/dal.py b/gluon/dal.py index 677d1139..50ff6656 100644 --- a/gluon/dal.py +++ b/gluon/dal.py @@ -8073,7 +8073,7 @@ def index(): # skip all non-empty lines for line in ifile: if not line.strip(): - breal + break else: raise RuntimeError("Unable to import table that does not exist.\nTry db.import_from_csv_file(..., map_tablenames={'table':'othertable'},ignore_missing_tables=True)") From c0600a9e2e60ca22503c945c6fec9e90d4bb0586 Mon Sep 17 00:00:00 2001 From: niphlod Date: Fri, 2 Aug 2013 21:14:00 +0200 Subject: [PATCH 8/9] fixed bug introduced with commit: df4d8a523860eaa60547539188faf4fed1d7cf74 --- gluon/dal.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/gluon/dal.py b/gluon/dal.py index 677d1139..bdbfc906 100644 --- a/gluon/dal.py +++ b/gluon/dal.py @@ -567,7 +567,7 @@ class ConnectionPool(object): """ this actually does not make the folder. it has to be there """ self.folder = getattr(THREAD_LOCAL,'folder','') - if (os.path.isabs(self.folder) and + if (os.path.isabs(self.folder) and isinstance(self, UseDatabaseStoredFile) and self.folder.startswith(os.getcwd())): self.folder = os.path.relpath(self.folder, os.getcwd()) @@ -1676,7 +1676,7 @@ class BaseAdapter(ConnectionPool): sql_o += ' ORDER BY %s' % self.RANDOM() else: sql_o += ' ORDER BY %s' % self.expand(orderby) - if (limitby and not groupby and tablenames and orderby_on_limitby): + if (limitby and not groupby and tablenames and orderby_on_limitby and not orderby): sql_o += ' ORDER BY %s' % ', '.join(['%s.%s'%(t,x) for t in tablenames for x in (hasattr(self.db[t],'_primarykey') and self.db[t]._primarykey or [self.db[t]._id.name])]) # oracle does not support limitby sql = self.select_limitby(sql_s, sql_f, sql_t, sql_w, sql_o, limitby) @@ -8367,7 +8367,7 @@ class Table(object): self._references = [] for field in self: fieldname = field.name - field_type = field.type + field_type = field.type if isinstance(field_type,str) and field_type[:10] == 'reference ': ref = field_type[10:].strip() if not ref.strip(): @@ -8375,7 +8375,7 @@ class Table(object): if '.' in ref: rtablename, rfieldname = ref.split('.',1) else: - rtablename, rfieldname = ref, None + rtablename, rfieldname = ref, None if not rtablename in db: pr[rtablename] = pr.get(rtablename,[]) + [field] continue @@ -8398,7 +8398,7 @@ class Table(object): field.referent = None for referee in pr.get(self._tablename,[]): self._referenced_by.append(referee) - + def _filter_fields(self, record, id=False): return dict([(k, v) for (k, v) in record.iteritems() if k in self.fields and (self[k].type!='id' or id)]) From 6cb91ab60d2984fcb5ff32e4c2b109b57b5e2eae Mon Sep 17 00:00:00 2001 From: niphlod Date: Sat, 3 Aug 2013 02:46:59 +0200 Subject: [PATCH 9/9] fix issue with sys.path. Now the path looks like 'root/gluon', 'root/site-packages', 'root' like in a normal web request --- gluon/scheduler.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/gluon/scheduler.py b/gluon/scheduler.py index 95820ffa..92036210 100644 --- a/gluon/scheduler.py +++ b/gluon/scheduler.py @@ -73,13 +73,10 @@ import optparse import types import Queue -if 'WEB2PY_PATH' in os.environ: - sys.path.append(os.environ['WEB2PY_PATH']) -else: - os.environ['WEB2PY_PATH'] = os.getcwd() +path = os.getcwd() -if not os.environ['WEB2PY_PATH'] in sys.path: - sys.path.append(os.environ['WEB2PY_PATH']) +if 'WEB2PY_PATH' not in os.environ: + os.environ['WEB2PY_PATH'] = path try: from gluon.contrib.simplejson import loads, dumps @@ -90,7 +87,8 @@ IDENTIFIER = "%s#%s" % (socket.gethostname(),os.getpid()) logger = logging.getLogger('web2py.scheduler.%s' % IDENTIFIER) -from gluon import DAL, Field, IS_NOT_EMPTY, IS_IN_SET, IS_NOT_IN_DB, IS_INT_IN_RANGE, IS_DATETIME +from gluon import DAL, Field, IS_NOT_EMPTY, IS_IN_SET, IS_NOT_IN_DB +from gluon import IS_INT_IN_RANGE, IS_DATETIME from gluon.utils import web2py_uuid from gluon.storage import Storage @@ -254,6 +252,7 @@ class MetaScheduler(threading.Thread): self.have_heartbeat = True # set to False to kill self.empty_runs = 0 + def async(self, task): """ starts the background process and returns: