diff --git a/gluon/tools.py b/gluon/tools.py index 98675cfe..f4d1f11b 100644 --- a/gluon/tools.py +++ b/gluon/tools.py @@ -2,8 +2,12 @@ # -*- coding: utf-8 -*- """ -This file is part of the web2py Web Framework Copyrighted by Massimo Di Pierro -License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) +| This file is part of the web2py Web Framework +| Copyrighted by Massimo Di Pierro +| License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) + +Auth, Mail, PluginManager and various utilities +------------------------------------------------ """ import base64 @@ -108,54 +112,108 @@ class Mail(object): body, multiple attachments and encryption support Works with SMTP and Google App Engine. + + Args: + server: SMTP server address in address:port notation + sender: sender email address + login: sender login name and password in login:password notation + or None if no authentication is required + tls: enables/disables encryption (True by default) + + In Google App Engine use :: + + server='gae' + + For sake of backward compatibility all fields are optional and default + to None, however, to be able to send emails at least server and sender + must be specified. They are available under following fields:: + + mail.settings.server + mail.settings.sender + mail.settings.login + + When server is 'logging', email is logged but not sent (debug mode) + + Optionally you can use PGP encryption or X509:: + + mail.settings.cipher_type = None + mail.settings.gpg_home = None + mail.settings.sign = True + mail.settings.sign_passphrase = None + mail.settings.encrypt = True + mail.settings.x509_sign_keyfile = None + mail.settings.x509_sign_certfile = None + mail.settings.x509_nocerts = False + mail.settings.x509_crypt_certfiles = None + + cipher_type : None + gpg - need a python-pyme package and gpgme lib + x509 - smime + gpg_home : you can set a GNUPGHOME environment variable + to specify home of gnupg + sign : sign the message (True or False) + sign_passphrase : passphrase for key signing + encrypt : encrypt the message + ... x509 only ... + x509_sign_keyfile : the signers private key filename (PEM format) + x509_sign_certfile: the signers certificate filename (PEM format) + x509_nocerts : if True then no attached certificate in mail + x509_crypt_certfiles: the certificates file to encrypt the messages + with can be a file name or a list of + file names (PEM format) + + Examples: + Create Mail object with authentication data for remote server:: + + mail = Mail('example.com:25', 'me@example.com', 'me:password') """ class Attachment(MIMEBase.MIMEBase): """ Email attachment - Arguments: - + Args: payload: path to file or file-like object with read() method filename: name of the attachment stored in message; if set to - None, it will be fetched from payload path; file-like - object payload must have explicit filename specified + None, it will be fetched from payload path; file-like + object payload must have explicit filename specified content_id: id of the attachment; automatically contained within - < and > + `<` and `>` content_type: content type of the attachment; if set to None, - it will be fetched from filename using gluon.contenttype - module + it will be fetched from filename using gluon.contenttype + module encoding: encoding of all strings passed to this function (except - attachment body) + attachment body) Content ID is used to identify attachments within the html body; in example, attached image with content ID 'photo' may be used in - html message as a source of img tag . + html message as a source of img tag ``. - Examples: + Example:: + Create attachment from text file:: - #Create attachment from text file: - attachment = Mail.Attachment('/path/to/file.txt') + attachment = Mail.Attachment('/path/to/file.txt') - Content-Type: text/plain - MIME-Version: 1.0 - Content-Disposition: attachment; filename="file.txt" - Content-Transfer-Encoding: base64 + Content-Type: text/plain + MIME-Version: 1.0 + Content-Disposition: attachment; filename="file.txt" + Content-Transfer-Encoding: base64 - SOMEBASE64CONTENT= + SOMEBASE64CONTENT= - #Create attachment from image file with custom filename and cid: - attachment = Mail.Attachment('/path/to/file.png', - filename='photo.png', - content_id='photo') + Create attachment from image file with custom filename and cid:: - Content-Type: image/png - MIME-Version: 1.0 - Content-Disposition: attachment; filename="photo.png" - Content-Id: - Content-Transfer-Encoding: base64 + attachment = Mail.Attachment('/path/to/file.png', + filename='photo.png', + content_id='photo') - SOMEOTHERBASE64CONTENT= + Content-Type: image/png + MIME-Version: 1.0 + Content-Disposition: attachment; filename="photo.png" + Content-Id: + Content-Transfer-Encoding: base64 + + SOMEOTHERBASE64CONTENT= """ def __init__( @@ -186,64 +244,6 @@ class Mail(object): Encoders.encode_base64(self) def __init__(self, server=None, sender=None, login=None, tls=True): - """ - Main Mail object - - Arguments: - - server: SMTP server address in address:port notation - sender: sender email address - login: sender login name and password in login:password notation - or None if no authentication is required - tls: enables/disables encryption (True by default) - - In Google App Engine use: - - server='gae' - - For sake of backward compatibility all fields are optional and default - to None, however, to be able to send emails at least server and sender - must be specified. They are available under following fields: - - mail.settings.server - mail.settings.sender - mail.settings.login - - When server is 'logging', email is logged but not sent (debug mode) - - Optionally you can use PGP encryption or X509: - - mail.settings.cipher_type = None - mail.settings.gpg_home = None - mail.settings.sign = True - mail.settings.sign_passphrase = None - mail.settings.encrypt = True - mail.settings.x509_sign_keyfile = None - mail.settings.x509_sign_certfile = None - mail.settings.x509_nocerts = False - mail.settings.x509_crypt_certfiles = None - - cipher_type : None - gpg - need a python-pyme package and gpgme lib - x509 - smime - gpg_home : you can set a GNUPGHOME environment variable - to specify home of gnupg - sign : sign the message (True or False) - sign_passphrase : passphrase for key signing - encrypt : encrypt the message - ... x509 only ... - x509_sign_keyfile : the signers private key filename (PEM format) - x509_sign_certfile: the signers certificate filename (PEM format) - x509_nocerts : if True then no attached certificate in mail - x509_crypt_certfiles: the certificates file to encrypt the messages - with can be a file name or a list of - file names (PEM format) - - Examples: - - #Create Mail object with authentication data for remote server: - mail = Mail('example.com:25', 'me@example.com', 'me:password') - """ settings = self.settings = Settings() settings.server = server @@ -284,73 +284,82 @@ class Mail(object): """ Sends an email using data specified in constructor - Arguments: - + Args: to: list or tuple of receiver addresses; will also accept single object subject: subject of the email message: email body text; depends on type of passed object: - if 2-list or 2-tuple is passed: first element will be - source of plain text while second of html text; - otherwise: object will be the only source of plain text - and html source will be set to None; - If text or html source is: - None: content part will be ignored, - string: content part will be set to it, - file-like object: content part will be fetched from - it using it's read() method + + - if 2-list or 2-tuple is passed: first element will be + source of plain text while second of html text; + - otherwise: object will be the only source of plain text + and html source will be set to None + + If text or html source is: + + - None: content part will be ignored, + - string: content part will be set to it, + - file-like object: content part will be fetched from it using + it's read() method attachments: list or tuple of Mail.Attachment objects; will also - accept single object + accept single object cc: list or tuple of carbon copy receiver addresses; will also accept single object bcc: list or tuple of blind carbon copy receiver addresses; will also accept single object reply_to: address to which reply should be composed encoding: encoding of all strings passed to this method (including - message bodies) + message bodies) headers: dictionary of headers to refine the headers just before - sending mail, e.g. {'X-Mailer' : 'web2py mailer'} - from_address: address to appear in the 'From:' header, this is not the - envelope sender. If not specified the sender will be used + sending mail, e.g. `{'X-Mailer' : 'web2py mailer'}` + from_address: address to appear in the 'From:' header, this is not + the envelope sender. If not specified the sender will be used + Examples: + Send plain text message to single address:: - #Send plain text message to single address: - mail.send('you@example.com', - 'Message subject', - 'Plain text body of the message') + mail.send('you@example.com', + 'Message subject', + 'Plain text body of the message') - #Send html message to single address: - mail.send('you@example.com', - 'Message subject', - 'Plain text body of the message') + Send html message to single address:: - #Send text and html message to three addresses (two in cc): - mail.send('you@example.com', - 'Message subject', - ('Plain text body', 'html body'), - cc=['other1@example.com', 'other2@example.com']) + mail.send('you@example.com', + 'Message subject', + 'Plain text body of the message') - #Send html only message with image attachment available from - the message by 'photo' content id: - mail.send('you@example.com', - 'Message subject', - (None, ''), - Mail.Attachment('/path/to/photo.jpg' - content_id='photo')) + Send text and html message to three addresses (two in cc):: - #Send email with two attachments and no body text - mail.send('you@example.com, - 'Message subject', - None, - [Mail.Attachment('/path/to/fist.file'), - Mail.Attachment('/path/to/second.file')]) + mail.send('you@example.com', + 'Message subject', + ('Plain text body', 'html body'), + cc=['other1@example.com', 'other2@example.com']) - Returns True on success, False on failure. + Send html only message with image attachment available from the + message by 'photo' content id:: + + mail.send('you@example.com', + 'Message subject', + (None, ''), + Mail.Attachment('/path/to/photo.jpg' + content_id='photo')) + + Send email with two attachments and no body text:: + + mail.send('you@example.com, + 'Message subject', + None, + [Mail.Attachment('/path/to/fist.file'), + Mail.Attachment('/path/to/second.file')]) + + Returns: + True on success, False on failure. Before return, method updates two object's fields: - self.result: return value of smtplib.SMTP.sendmail() or GAE's - mail.send_mail() method - self.error: Exception message or None if above was successful + + - self.result: return value of smtplib.SMTP.sendmail() or GAE's + mail.send_mail() method + - self.error: Exception message or None if above was successful """ # We don't want to use base64 encoding for unicode mail @@ -733,14 +742,16 @@ class Mail(object): class Recaptcha(DIV): """ - Usage: + Examples: + Use as:: - form = FORM(Recaptcha(public_key='...',private_key='...')) + form = FORM(Recaptcha(public_key='...',private_key='...')) - or + or:: + + form = SQLFORM(...) + form.append(Recaptcha(public_key='...',private_key='...')) - form = SQLFORM(...) - form.append(Recaptcha(public_key='...',private_key='...')) """ API_SSL_SERVER = 'https://www.google.com/recaptcha/api' @@ -1064,7 +1075,16 @@ class Auth(object): - role creation and assignment - user defined group/role based permission - Authentication Example: + Args: + + environment: is there for legacy but unused (awful) + db: has to be the database where to create tables for authentication + mailer: `Mail(...)` or None (no mailer) or True (make a mailer) + hmac_key: can be a hmac_key or hmac_key=Auth.get_or_create_key() + controller: (where is the user action?) + cas_provider: (delegate authentication to the URL, CAS2) + + Authentication Example:: from gluon.contrib.utils import * mail=Mail() @@ -1078,54 +1098,54 @@ class Auth(object): def authentication(): return dict(form=auth()) - exposes: + Exposes: - - http://.../{application}/{controller}/authentication/login - - http://.../{application}/{controller}/authentication/logout - - http://.../{application}/{controller}/authentication/register - - http://.../{application}/{controller}/authentication/verify_email - - http://.../{application}/{controller}/authentication/retrieve_username - - http://.../{application}/{controller}/authentication/retrieve_password - - http://.../{application}/{controller}/authentication/reset_password - - http://.../{application}/{controller}/authentication/profile - - http://.../{application}/{controller}/authentication/change_password + - `http://.../{application}/{controller}/authentication/login` + - `http://.../{application}/{controller}/authentication/logout` + - `http://.../{application}/{controller}/authentication/register` + - `http://.../{application}/{controller}/authentication/verify_email` + - `http://.../{application}/{controller}/authentication/retrieve_username` + - `http://.../{application}/{controller}/authentication/retrieve_password` + - `http://.../{application}/{controller}/authentication/reset_password` + - `http://.../{application}/{controller}/authentication/profile` + - `http://.../{application}/{controller}/authentication/change_password` On registration a group with role=new_user.id is created and user is given membership of this group. - You can create a group with: + You can create a group with:: group_id=auth.add_group('Manager', 'can access the manage action') auth.add_permission(group_id, 'access to manage') - Here \"access to manage\" is just a user defined string. - You can give access to a user: + Here "access to manage" is just a user defined string. + You can give access to a user:: auth.add_membership(group_id, user_id) If user id is omitted, the logged in user is assumed - Then you can decorate any action: + Then you can decorate any action:: @auth.requires_permission('access to manage') def manage(): return dict() - You can restrict a permission to a specific table: + You can restrict a permission to a specific table:: auth.add_permission(group_id, 'edit', db.sometable) @auth.requires_permission('edit', db.sometable) - Or to a specific record: + Or to a specific record:: auth.add_permission(group_id, 'edit', db.sometable, 45) @auth.requires_permission('edit', db.sometable, 45) - If authorization is not granted calls: + If authorization is not granted calls:: auth.settings.on_failed_authorization - Other options: + Other options:: auth.settings.mailer=None auth.settings.expiration=3600 # seconds @@ -1134,6 +1154,7 @@ class Auth(object): ### these are messages that can be customized ... + """ @staticmethod @@ -1163,16 +1184,7 @@ class Auth(object): hmac_key=None, controller='default', function='user', cas_provider=None, signature=True, secure=False, csrf_prevention=True, propagate_extension=None): - """ - auth=Auth(db) - - environment is there for legacy but unused (awful) - - db has to be the database where to create tables for authentication - - mailer=Mail(...) or None (no mailed) or True (make a mailer) - - hmac_key can be a hmac_key or hmac_key=Auth.get_or_create_key() - - controller (where is the user action?) - - cas_provider (delegate authentication to the URL, CAS2) - """ ## next two lines for backward compatibility if not db and environment and isinstance(environment, DAL): db = environment @@ -1185,7 +1197,7 @@ class Auth(object): self.user_groups = auth and auth.user_groups or {} if secure: request.requires_https() - now = request.now + now = request.now # if we have auth info # if not expired it, used it # if expired, clear the session @@ -1201,11 +1213,11 @@ class Auth(object): self.user = None if session.auth: del session.auth - session.renew(clear_session=True) + session.renew(clear_session=True) else: self.user = None if session.auth: - del session.auth + del session.auth # ## what happens after login? url_index = URL(controller, 'index') @@ -1325,9 +1337,12 @@ class Auth(object): def __call__(self): """ - usage: + Example: + Use as:: + + def authentication(): + return dict(form=auth()) - def authentication(): return dict(form=auth()) """ request = current.request @@ -1365,8 +1380,8 @@ class Auth(object): """ Navbar with support for more templates This uses some code from the old navbar. - Keyword arguments: - mode -- see options for list of + Args: + mode: see options for list of """ items = [] # Hold all menu items in a list @@ -1461,62 +1476,61 @@ class Auth(object): _href='#',_class="dropdown-toggle", data={'toggle':'dropdown'}), self.bar, _class='dropdown') - - def bare(): """ In order to do advanced customization we only need the prefix, the user_identifier and the href attribute of items - Example: + Examples: + Use as:: - # in module custom_layout.py - from gluon import * - def navbar(auth_navbar): - bar = auth_navbar - user = bar["user"] + # in module custom_layout.py + from gluon import * + def navbar(auth_navbar): + bar = auth_navbar + user = bar["user"] - if not user: - btn_login = A(current.T("Login"), - _href=bar["login"], - _class="btn btn-success", - _rel="nofollow") - btn_register = A(current.T("Sign up"), - _href=bar["register"], - _class="btn btn-primary", - _rel="nofollow") - return DIV(btn_register, btn_login, _class="btn-group") - else: - toggletext = "%s back %s" % (bar["prefix"], user) - toggle = A(toggletext, - _href="#", - _class="dropdown-toggle", - _rel="nofollow", - **{"_data-toggle": "dropdown"}) - li_profile = LI(A(I(_class="icon-user"), ' ', - current.T("Account details"), - _href=bar["profile"], _rel="nofollow")) - li_custom = LI(A(I(_class="icon-book"), ' ', - current.T("My Agenda"), - _href="#", rel="nofollow")) - li_logout = LI(A(I(_class="icon-off"), ' ', - current.T("logout"), - _href=bar["logout"], _rel="nofollow")) - dropdown = UL(li_profile, - li_custom, - LI('', _class="divider"), - li_logout, - _class="dropdown-menu", _role="menu") + if not user: + btn_login = A(current.T("Login"), + _href=bar["login"], + _class="btn btn-success", + _rel="nofollow") + btn_register = A(current.T("Sign up"), + _href=bar["register"], + _class="btn btn-primary", + _rel="nofollow") + return DIV(btn_register, btn_login, _class="btn-group") + else: + toggletext = "%s back %s" % (bar["prefix"], user) + toggle = A(toggletext, + _href="#", + _class="dropdown-toggle", + _rel="nofollow", + **{"_data-toggle": "dropdown"}) + li_profile = LI(A(I(_class="icon-user"), ' ', + current.T("Account details"), + _href=bar["profile"], _rel="nofollow")) + li_custom = LI(A(I(_class="icon-book"), ' ', + current.T("My Agenda"), + _href="#", rel="nofollow")) + li_logout = LI(A(I(_class="icon-off"), ' ', + current.T("logout"), + _href=bar["logout"], _rel="nofollow")) + dropdown = UL(li_profile, + li_custom, + LI('', _class="divider"), + li_logout, + _class="dropdown-menu", _role="menu") - return LI(toggle, dropdown, _class="dropdown") + return LI(toggle, dropdown, _class="dropdown") - # in models db.py - import custom_layout as custom + # in models db.py + import custom_layout as custom - # in layout.html - + # in layout.html + """ bare = {} @@ -1584,13 +1598,13 @@ class Auth(object): current_record='current_record', current_record_label=None): """ - to enable full record versioning (including auth tables): + Used to enable full record versioning (including auth tables):: - auth = Auth(db) - auth.define_tables(signature=True) - # define our own tables - db.define_table('mything',Field('name'),auth.signature) - auth.enable_record_versioning(tables=db) + auth = Auth(db) + auth.define_tables(signature=True) + # define our own tables + db.define_table('mything',Field('name'),auth.signature) + auth.enable_record_versioning(tables=db) tables can be the db (all table) or a list of tables. only tables with modified_by and modified_on fiels (as created @@ -1603,10 +1617,11 @@ class Auth(object): enable_record_versioning enables a common_filter for every table that filters out records with is_active = False - Important: If you use auth.enable_record_versioning, - do not use auth.archive or you will end up with duplicates. - auth.archive does explicitly what enable_record_versioning - does automatically. + Note: + If you use auth.enable_record_versioning, + do not use auth.archive or you will end up with duplicates. + auth.archive does explicitly what enable_record_versioning + does automatically. """ current_record_label = current_record_label or current.T( @@ -1668,16 +1683,17 @@ class Auth(object): def define_tables(self, username=None, signature=None, migrate=None, fake_migrate=None): """ - to be called unless tables are defined manually + To be called unless tables are defined manually - usages: + Examples: + Use as:: - # defines all needed tables and table files - # 'myprefix_auth_user.table', ... - auth.define_tables(migrate='myprefix_') + # defines all needed tables and table files + # 'myprefix_auth_user.table', ... + auth.define_tables(migrate='myprefix_') - # defines all needed tables without migration/table files - auth.define_tables(migrate=False) + # defines all needed tables without migration/table files + auth.define_tables(migrate=False) """ @@ -1907,9 +1923,11 @@ class Auth(object): def log_event(self, description, vars=None, origin='auth'): """ - usage: + Examples: + Use as:: + + auth.log_event(description='this happened', origin='auth') - auth.log_event(description='this happened', origin='auth') """ if not self.settings.logging_enabled or not description: return @@ -1929,8 +1947,8 @@ class Auth(object): login=True, get=True): """ Used for alternate login methods: - If the user exists already then password is updated. - If the user doesn't yet exist, then they are created. + If the user exists already then password is updated. + If the user doesn't yet exist, then they are created. """ table_user = self.table_user() user = None @@ -1982,10 +2000,11 @@ class Auth(object): def basic(self, basic_auth_realm=False): """ - perform basic login. + Performs basic login. - :param basic_auth_realm: optional basic http authentication realm. - :type basic_auth_realm: str or unicode or function or callable or boolean. + Args: + basic_auth_realm: optional basic http authentication realm. Can take + str or unicode or function or callable or boolean. reads current.request.env.http_authorization and returns basic_allowed,basic_accepted,user. @@ -2022,7 +2041,7 @@ class Auth(object): def login_user(self, user): """ - login the user = db.auth_user(id) + Logins the `user = db.auth_user(id)` """ from gluon.settings import global_settings if global_settings.web2py_runtime_gae: @@ -2054,7 +2073,7 @@ class Auth(object): def login_bare(self, username, password): """ - logins user as specified by username (or email) and password + Logins user as specified by username (or email) and password """ settings = self._get_login_settings() user = settings.table_user(**{settings.userfield: \ @@ -2062,8 +2081,8 @@ class Auth(object): if user and user.get(settings.passfield, False): password = settings.table_user[ settings.passfield].validate(password)[0] - if ((user.registration_key is None or - not user.registration_key.strip()) and + if ((user.registration_key is None or + not user.registration_key.strip()) and password == user[settings.passfield]): self.login_user(user) return user @@ -2078,7 +2097,7 @@ class Auth(object): def register_bare(self, **fields): """ - registers a user as specified by username (or email) + Registers a user as specified by username (or email) and a raw password. """ settings = self._get_login_settings() @@ -2203,11 +2222,7 @@ class Auth(object): log=DEFAULT, ): """ - returns a login form - - method: Auth.login([next=DEFAULT [, onvalidation=DEFAULT - [, onaccept=DEFAULT [, log=DEFAULT]]]]) - + Returns a login form """ table_user = self.table_user() @@ -2451,11 +2466,7 @@ class Auth(object): def logout(self, next=DEFAULT, onlogout=DEFAULT, log=DEFAULT): """ - logout and redirects to login - - method: Auth.logout ([next=DEFAULT[, onlogout=DEFAULT[, - log=DEFAULT]]]) - + Logouts and redirects to login """ if next is DEFAULT: @@ -2489,11 +2500,7 @@ class Auth(object): log=DEFAULT, ): """ - returns a registration form - - method: Auth.register([next=DEFAULT [, onvalidation=DEFAULT - [, onaccept=DEFAULT [, log=DEFAULT]]]]) - + Returns a registration form """ table_user = self.table_user() @@ -2602,7 +2609,7 @@ class Auth(object): d.update(dict(key=key, link=link,username=form.vars[username])) if not (self.settings.mailer and self.settings.mailer.send( to=form.vars.email, - subject=self.messages.verify_email_subject, + subject=self.messages.verify_email_subject, message=self.messages.verify_email % d)): self.db.rollback() response.flash = self.messages.unable_send_email @@ -2631,8 +2638,8 @@ class Auth(object): def is_logged_in(self): """ - checks if the user is logged in and returns True/False. - if so user is in auth.user as well as in session.auth.user + Checks if the user is logged in and returns True/False. + If so user is in auth.user as well as in session.auth.user """ if self.user: @@ -2646,11 +2653,7 @@ class Auth(object): log=DEFAULT, ): """ - action user to verify the registration email, XXXXXXXXXXXXXXXX - - method: Auth.verify_email([next=DEFAULT [, onvalidation=DEFAULT - [, onaccept=DEFAULT [, log=DEFAULT]]]]) - + Action used to verify the registration email """ key = getarg(-1) @@ -2685,12 +2688,8 @@ class Auth(object): log=DEFAULT, ): """ - returns a form to retrieve the user username + Returns a form to retrieve the user username (only if there is a username field) - - method: Auth.retrieve_username([next=DEFAULT - [, onvalidation=DEFAULT [, onaccept=DEFAULT [, log=DEFAULT]]]]) - """ table_user = self.table_user() @@ -2773,11 +2772,7 @@ class Auth(object): log=DEFAULT, ): """ - returns a form to reset the user password (deprecated) - - method: Auth.reset_password_deprecated([next=DEFAULT - [, onvalidation=DEFAULT [, onaccept=DEFAULT [, log=DEFAULT]]]]) - + Returns a form to reset the user password (deprecated) """ table_user = self.table_user() @@ -2852,11 +2847,7 @@ class Auth(object): log=DEFAULT, ): """ - returns a form to reset the user password - - method: Auth.reset_password([next=DEFAULT - [, onvalidation=DEFAULT [, onaccept=DEFAULT [, log=DEFAULT]]]]) - + Returns a form to reset the user password """ table_user = self.table_user() @@ -2912,11 +2903,7 @@ class Auth(object): log=DEFAULT, ): """ - returns a form to reset the user password - - method: Auth.reset_password([next=DEFAULT - [, onvalidation=DEFAULT [, onaccept=DEFAULT [, log=DEFAULT]]]]) - + Returns a form to reset the user password """ table_user = self.table_user() request = current.request @@ -3021,10 +3008,7 @@ class Auth(object): log=DEFAULT, ): """ - returns a form that lets the user change password - - method: Auth.change_password([next=DEFAULT[, onvalidation=DEFAULT[, - onaccept=DEFAULT[, log=DEFAULT]]]]) + Returns a form that lets the user change password """ if not self.is_logged_in(): @@ -3090,11 +3074,7 @@ class Auth(object): log=DEFAULT, ): """ - returns a form that lets the user change his/her profile - - method: Auth.profile([next=DEFAULT [, onvalidation=DEFAULT - [, onaccept=DEFAULT [, log=DEFAULT]]]]) - + Returns a form that lets the user change his/her profile """ table_user = self.table_user() @@ -3157,11 +3137,15 @@ class Auth(object): def impersonate(self, user_id=DEFAULT): """ - usage: POST TO http://..../impersonate request.post_vars.user_id= - set request.post_vars.user_id to 0 to restore original user. + To use this make a POST to + `http://..../impersonate request.post_vars.user_id=` + + Set request.post_vars.user_id to 0 to restore original user. + + requires impersonator is logged in and:: + + has_permission('impersonate', 'auth_user', user_id) - requires impersonator is logged in and - has_permission('impersonate', 'auth_user', user_id) """ request = current.request session = current.session @@ -3185,7 +3169,7 @@ class Auth(object): auth.user.update( table_user._filter_fields(user, True)) self.user = auth.user - self.update_groups() + self.update_groups() log = self.messages['impersonate_log'] self.log_event(log, dict(id=current_id, other_id=auth.user.id)) self.run_login_onaccept() @@ -3218,7 +3202,7 @@ class Auth(object): def groups(self): """ - displays the groups and their roles for the logged in user + Displays the groups and their roles for the logged in user """ if not self.is_logged_in(): @@ -3240,7 +3224,7 @@ class Auth(object): def not_authorized(self): """ - you can change the view for this page to make it look as you like + You can change the view for this page to make it look as you like """ if current.request.ajax: raise HTTP(403, 'ACCESS DENIED') @@ -3248,7 +3232,7 @@ class Auth(object): def requires(self, condition, requires_login=True, otherwise=None): """ - decorator that prevents access to action if not logged in + Decorator that prevents access to action if not logged in """ def decorator(action): @@ -3294,13 +3278,13 @@ class Auth(object): def requires_login(self, otherwise=None): """ - decorator that prevents access to action if not logged in + Decorator that prevents access to action if not logged in """ return self.requires(True, otherwise=otherwise) def requires_membership(self, role=None, group_id=None, otherwise=None): """ - decorator that prevents access to action if not logged in or + Decorator that prevents access to action if not logged in or if user logged in is not a member of group_id. If role is provided instead of group_id then the group_id is calculated. @@ -3312,7 +3296,7 @@ class Auth(object): def requires_permission(self, name, table_name='', record_id=0, otherwise=None): """ - decorator that prevents access to action if not logged in or + Decorator that prevents access to action if not logged in or if user logged in is not a member of any group (role) that has 'name' access to 'table_name', 'record_id'. """ @@ -3322,7 +3306,7 @@ class Auth(object): def requires_signature(self, otherwise=None, hash_vars=True): """ - decorator that prevents access to action if not logged in or + Decorator that prevents access to action if not logged in or if user logged in is not a member of group_id. If role is provided instead of group_id then the group_id is calculated. @@ -3333,7 +3317,7 @@ class Auth(object): def add_group(self, role, description=''): """ - creates a group associated to a role + Creates a group associated to a role """ group_id = self.table_group().insert( @@ -3344,7 +3328,7 @@ class Auth(object): def del_group(self, group_id): """ - deletes a group + Deletes a group """ self.db(self.table_group().id == group_id).delete() self.db(self.table_membership().group_id == group_id).delete() @@ -3354,7 +3338,7 @@ class Auth(object): def id_group(self, role): """ - returns the group_id of the group specified by the role + Returns the group_id of the group specified by the role """ rows = self.db(self.table_group().role == role).select() if not rows: @@ -3363,8 +3347,8 @@ class Auth(object): def user_group(self, user_id=None): """ - returns the group_id of the group uniquely associated to this user - i.e. role=user:[user_id] + Returns the group_id of the group uniquely associated to this user + i.e. `role=user:[user_id]` """ return self.id_group(self.user_group_role(user_id)) @@ -3379,7 +3363,7 @@ class Auth(object): def has_membership(self, group_id=None, user_id=None, role=None): """ - checks if user is member of group_id or role + Checks if user is member of group_id or role """ group_id = group_id or self.id_group(role) @@ -3401,7 +3385,7 @@ class Auth(object): def add_membership(self, group_id=None, user_id=None, role=None): """ - gives user_id membership of group_id or role + Gives user_id membership of group_id or role if user is None than user_id is that of current logged in user """ @@ -3425,7 +3409,7 @@ class Auth(object): def del_membership(self, group_id=None, user_id=None, role=None): """ - revokes membership from group_id to user_id + Revokes membership from group_id to user_id if user_id is None than user_id is that of current logged in user """ @@ -3450,7 +3434,7 @@ class Auth(object): group_id=None, ): """ - checks if user_id or current logged in user is member of a group + Checks if user_id or current logged in user is member of a group that has 'name' permission on 'table_name' and 'record_id' if group_id is passed, it checks whether the group has the permission """ @@ -3502,7 +3486,7 @@ class Auth(object): record_id=0, ): """ - gives group_id 'name' access to 'table_name' and 'record_id' + Gives group_id 'name' access to 'table_name' and 'record_id' """ permission = self.table_permission() @@ -3530,7 +3514,7 @@ class Auth(object): record_id=0, ): """ - revokes group_id 'name' access to 'table_name' and 'record_id' + Revokes group_id 'name' access to 'table_name' and 'record_id' """ permission = self.table_permission() @@ -3544,13 +3528,14 @@ class Auth(object): def accessible_query(self, name, table, user_id=None): """ - returns a query with all accessible records for user_id or + Returns a query with all accessible records for user_id or the current logged in user this method does not work on GAE because uses JOIN and IN - example: + Example: + Use as:: - db(auth.accessible_query('read', db.mytable)).select(db.mytable.ALL) + db(auth.accessible_query('read', db.mytable)).select(db.mytable.ALL) """ if not user_id: @@ -3595,11 +3580,12 @@ class Auth(object): archive_current=False, fields=None): """ - If you have a table (db.mytable) that needs full revision history you can just do: + If you have a table (db.mytable) that needs full revision history you + can just do:: form=crud.update(db.mytable,myrecord,onaccept=auth.archive) - or + or:: form=SQLFORM(db.mytable,myrecord).process(onaccept=auth.archive) @@ -3612,7 +3598,7 @@ class Auth(object): fields allows to specify extra fields that need to be archived. If you want to access such table you need to define it yourself - in a model: + in a model:: db.define_table('mytable_archive', Field('current_record',db.mytable), @@ -3620,7 +3606,7 @@ class Auth(object): Notice such table includes all fields of db.mytable plus one: current_record. crud.archive does not timestamp the stored record unless your original table - has a fields like: + has a fields like:: db.define_table(..., Field('saved_on','datetime', @@ -3632,13 +3618,13 @@ class Auth(object): the record is archived. If you want to change the archive table name and the name of the reference field - you can do, for example: + you can do, for example:: db.define_table('myhistory', Field('parent_record',db.mytable), db.mytable) - and use it as: + and use it as:: form=crud.update(db.mytable,myrecord, onaccept=lambda form:crud.archive(form, @@ -3719,7 +3705,7 @@ class Auth(object): return wiki def wikimenu(self): - """to be used in menu.py for app wide wiki menus""" + """To be used in menu.py for app wide wiki menus""" if (hasattr(self, "_wiki") and self._wiki.settings.controller and self._wiki.settings.function): @@ -3730,7 +3716,7 @@ class Crud(object): def url(self, f=None, args=None, vars=None): """ - this should point to the controller that exposes + This should point to the controller that exposes download and crud """ if args is None: @@ -3851,12 +3837,6 @@ class Crud(object): formname=DEFAULT, **attributes ): - """ - method: Crud.update(table, record, [next=DEFAULT - [, onvalidation=DEFAULT [, onaccept=DEFAULT [, log=DEFAULT - [, message=DEFAULT[, deletable=DEFAULT]]]]]]) - - """ if not (isinstance(table, self.db.Table) or table in self.db.tables) \ or (isinstance(record, str) and not str(record).isdigit()): raise HTTP(404) @@ -3966,10 +3946,6 @@ class Crud(object): formname=DEFAULT, **attributes ): - """ - method: Crud.create(table, [next=DEFAULT [, onvalidation=DEFAULT - [, onaccept=DEFAULT [, log=DEFAULT[, message=DEFAULT]]]]]) - """ if next is DEFAULT: next = self.settings.create_next @@ -4023,10 +3999,6 @@ class Crud(object): next=DEFAULT, message=DEFAULT, ): - """ - method: Crud.delete(table, record_id, [next=DEFAULT - [, message=DEFAULT]]) - """ if not (isinstance(table, self.db.Table) or table in self.db.tables): raise HTTP(404) if not isinstance(table, self.db.Table): @@ -4151,16 +4123,19 @@ class Crud(object): def search(self, *tables, **args): """ Creates a search form and its results for a table - Example usage: - form, results = crud.search(db.test, - queries = ['equals', 'not equal', 'contains'], - query_labels={'equals':'Equals', - 'not equal':'Not equal'}, - fields = ['id','children'], - field_labels = { - 'id':'ID','children':'Children'}, - zero='Please choose', - query = (db.test.id > 0)&(db.test.id != 3) ) + Examples: + Use as:: + + form, results = crud.search(db.test, + queries = ['equals', 'not equal', 'contains'], + query_labels={'equals':'Equals', + 'not equal':'Not equal'}, + fields = ['id','children'], + field_labels = { + 'id':'ID','children':'Children'}, + zero='Please choose', + query = (db.test.id > 0)&(db.test.id != 3) ) + """ table = tables[0] fields = args.get('fields', table.fields) @@ -4341,18 +4316,19 @@ class Service(object): def run(self, f): """ - example: + Example: + Use as:: - service = Service() - @service.run - def myfunction(a, b): - return a + b - def call(): - return service() + service = Service() + @service.run + def myfunction(a, b): + return a + b + def call(): + return service() - Then call it with: + Then call it with:: - wget http://..../app/default/call/run/myfunction?a=3&b=4 + wget http://..../app/default/call/run/myfunction?a=3&b=4 """ self.run_procedures[f.__name__] = f @@ -4360,18 +4336,19 @@ class Service(object): def csv(self, f): """ - example: + Example: + Use as:: - service = Service() - @service.csv - def myfunction(a, b): - return a + b - def call(): - return service() + service = Service() + @service.csv + def myfunction(a, b): + return a + b + def call(): + return service() - Then call it with: + Then call it with:: - wget http://..../app/default/call/csv/myfunction?a=3&b=4 + wget http://..../app/default/call/csv/myfunction?a=3&b=4 """ self.run_procedures[f.__name__] = f @@ -4379,18 +4356,19 @@ class Service(object): def xml(self, f): """ - example: + Example: + Use as:: - service = Service() - @service.xml - def myfunction(a, b): - return a + b - def call(): - return service() + service = Service() + @service.xml + def myfunction(a, b): + return a + b + def call(): + return service() - Then call it with: + Then call it with:: - wget http://..../app/default/call/xml/myfunction?a=3&b=4 + wget http://..../app/default/call/xml/myfunction?a=3&b=4 """ self.run_procedures[f.__name__] = f @@ -4398,20 +4376,21 @@ class Service(object): def rss(self, f): """ - example: + Example: + Use as:: - service = Service() - @service.rss - def myfunction(): - return dict(title=..., link=..., description=..., - created_on=..., entries=[dict(title=..., link=..., - description=..., created_on=...]) - def call(): - return service() + service = Service() + @service.rss + def myfunction(): + return dict(title=..., link=..., description=..., + created_on=..., entries=[dict(title=..., link=..., + description=..., created_on=...]) + def call(): + return service() - Then call it with: + Then call it with: - wget http://..../app/default/call/rss/myfunction + wget http://..../app/default/call/rss/myfunction """ self.rss_procedures[f.__name__] = f @@ -4419,18 +4398,19 @@ class Service(object): def json(self, f): """ - example: + Example: + Use as:: - service = Service() - @service.json - def myfunction(a, b): - return [{a: b}] - def call(): - return service() + service = Service() + @service.json + def myfunction(a, b): + return [{a: b}] + def call(): + return service() - Then call it with: + Then call it with:; - wget http://..../app/default/call/json/myfunction?a=hello&b=world + wget http://..../app/default/call/json/myfunction?a=hello&b=world """ self.json_procedures[f.__name__] = f @@ -4438,18 +4418,19 @@ class Service(object): def jsonrpc(self, f): """ - example: + Example: + Use as:: - service = Service() - @service.jsonrpc - def myfunction(a, b): - return a + b - def call(): - return service() + service = Service() + @service.jsonrpc + def myfunction(a, b): + return a + b + def call(): + return service() - Then call it with: + Then call it with: - wget http://..../app/default/call/jsonrpc/myfunction?a=hello&b=world + wget http://..../app/default/call/jsonrpc/myfunction?a=hello&b=world """ self.jsonrpc_procedures[f.__name__] = f @@ -4457,18 +4438,19 @@ class Service(object): def jsonrpc2(self, f): """ - example: + Example: + Use as:: - service = Service() - @service.jsonrpc2 - def myfunction(a, b): - return a + b - def call(): - return service() + service = Service() + @service.jsonrpc2 + def myfunction(a, b): + return a + b + def call(): + return service() - Then call it with: + Then call it with: - wget --post-data '{"jsonrpc": "2.0", "id": 1, "method": "myfunction", "params": {"a": 1, "b": 2}}' http://..../app/default/call/jsonrpc2 + wget --post-data '{"jsonrpc": "2.0", "id": 1, "method": "myfunction", "params": {"a": 1, "b": 2}}' http://..../app/default/call/jsonrpc2 """ self.jsonrpc2_procedures[f.__name__] = f @@ -4476,18 +4458,19 @@ class Service(object): def xmlrpc(self, f): """ - example: + Example: + Use as:: - service = Service() - @service.xmlrpc - def myfunction(a, b): - return a + b - def call(): - return service() + service = Service() + @service.xmlrpc + def myfunction(a, b): + return a + b + def call(): + return service() - The call it with: + The call it with: - wget http://..../app/default/call/xmlrpc/myfunction?a=hello&b=world + wget http://..../app/default/call/xmlrpc/myfunction?a=hello&b=world """ self.xmlrpc_procedures[f.__name__] = f @@ -4495,18 +4478,20 @@ class Service(object): def amfrpc(self, f): """ - example: + Example: + Use as:: - service = Service() - @service.amfrpc - def myfunction(a, b): - return a + b - def call(): - return service() + service = Service() + @service.amfrpc + def myfunction(a, b): + return a + b + def call(): + return service() - The call it with: - wget http://..../app/default/call/amfrpc/myfunction?a=hello&b=world + Then call it with:: + + wget http://..../app/default/call/amfrpc/myfunction?a=hello&b=world """ self.amfrpc_procedures[f.__name__] = f @@ -4514,18 +4499,19 @@ class Service(object): def amfrpc3(self, domain='default'): """ - example: + Example: + Use as:: - service = Service() - @service.amfrpc3('domain') - def myfunction(a, b): - return a + b - def call(): - return service() + service = Service() + @service.amfrpc3('domain') + def myfunction(a, b): + return a + b + def call(): + return service() - The call it with: + Then call it with: - wget http://..../app/default/call/amfrpc3/myfunction?a=hello&b=world + wget http://..../app/default/call/amfrpc3/myfunction?a=hello&b=world """ if not isinstance(domain, str): @@ -4541,24 +4527,25 @@ class Service(object): def soap(self, name=None, returns=None, args=None, doc=None): """ - example: + Example: + Use as:: - service = Service() - @service.soap('MyFunction',returns={'result':int},args={'a':int,'b':int,}) - def myfunction(a, b): - return a + b - def call(): - return service() + service = Service() + @service.soap('MyFunction',returns={'result':int},args={'a':int,'b':int,}) + def myfunction(a, b): + return a + b + def call(): + return service() - The call it with: + Then call it with:: from gluon.contrib.pysimplesoap.client import SoapClient client = SoapClient(wsdl="http://..../app/default/call/soap?WSDL") response = client.MyFunction(a=1,b=2) return response['result'] - Exposes online generated documentation and xml example messages at: - - http://..../app/default/call/soap + It also exposes online generated documentation and xml example messages + at `http://..../app/default/call/soap` """ def _soap(f): @@ -4662,7 +4649,7 @@ class Service(object): # jsonrpc 2.0 error types. records the following structure {code: (message,meaning)} jsonrpc_errors = { - -32700: ("Parse error. Invalid JSON was received by the server.", "An error occurred on the server while parsing the JSON text."), + -32700: ("Parse error. Invalid JSON was received by the server.", "An error occurred on the server while parsing the JSON text."), -32600: ("Invalid Request", "The JSON sent is not a valid Request object."), -32601: ("Method not found", "The method does not exist / is not available."), -32602: ("Invalid params", "Invalid method parameter(s)."), @@ -4740,14 +4727,15 @@ class Service(object): """ Validate request as defined in: http://www.jsonrpc.org/specification#request_object. - :param data: The json object. - :type name: str. + Args: + data(str): The json object. - :returns: + Returns: - True -- if successful - False -- if no error should be reported (i.e. data is missing 'id' member) - :raises: JsonRPCException + Raises: + JsonRPCException """ @@ -4926,30 +4914,34 @@ class Service(object): def __call__(self): """ - register services with: - service = Service() - @service.run - @service.rss - @service.json - @service.jsonrpc - @service.xmlrpc - @service.amfrpc - @service.amfrpc3('domain') - @service.soap('Method', returns={'Result':int}, args={'a':int,'b':int,}) + Registers services with:: - expose services with + service = Service() + @service.run + @service.rss + @service.json + @service.jsonrpc + @service.xmlrpc + @service.amfrpc + @service.amfrpc3('domain') + @service.soap('Method', returns={'Result':int}, args={'a':int,'b':int,}) - def call(): return service() + Exposes services with:: + + def call(): + return service() + + You can call services with:: + + http://..../app/default/call/run?[parameters] + http://..../app/default/call/rss?[parameters] + http://..../app/default/call/json?[parameters] + http://..../app/default/call/jsonrpc + http://..../app/default/call/xmlrpc + http://..../app/default/call/amfrpc + http://..../app/default/call/amfrpc3 + http://..../app/default/call/soap - call services with - http://..../app/default/call/run?[parameters] - http://..../app/default/call/rss?[parameters] - http://..../app/default/call/json?[parameters] - http://..../app/default/call/jsonrpc - http://..../app/default/call/xmlrpc - http://..../app/default/call/amfrpc - http://..../app/default/call/amfrpc3 - http://..../app/default/call/soap """ request = current.request @@ -4987,12 +4979,15 @@ class Service(object): def completion(callback): """ - Executes a task on completion of the called action. For example: + Executes a task on completion of the called action. - from gluon.tools import completion - @completion(lambda d: logging.info(repr(d))) - def index(): - return dict(message='hello') + Example: + Use as:: + + from gluon.tools import completion + @completion(lambda d: logging.info(repr(d))) + def index(): + return dict(message='hello') It logs the output of the function every time input is called. The argument of completion is executed in a new thread. @@ -5077,55 +5072,60 @@ def test_thread_separation(): class PluginManager(object): """ - Plugin Manager is similar to a storage object but it is a single level singleton - this means that multiple instances within the same thread share the same attributes - Its constructor is also special. The first argument is the name of the plugin you are defining. + Plugin Manager is similar to a storage object but it is a single level + singleton. This means that multiple instances within the same thread share + the same attributes. + Its constructor is also special. The first argument is the name of the + plugin you are defining. The named arguments are parameters needed by the plugin with default values. If the parameters were previous defined, the old values are used. - For example: + Example: + in some general configuration file:: - ### in some general configuration file: - >>> plugins = PluginManager() - >>> plugins.me.param1=3 + plugins = PluginManager() + plugins.me.param1=3 - ### within the plugin model - >>> _ = PluginManager('me',param1=5,param2=6,param3=7) + within the plugin model:: - ### where the plugin is used - >>> print plugins.me.param1 - 3 - >>> print plugins.me.param2 - 6 - >>> plugins.me.param3 = 8 - >>> print plugins.me.param3 - 8 + _ = PluginManager('me',param1=5,param2=6,param3=7) - Here are some tests: + where the plugin is used:: + + >>> print plugins.me.param1 + 3 + >>> print plugins.me.param2 + 6 + >>> plugins.me.param3 = 8 + >>> print plugins.me.param3 + 8 + + Here are some tests:: + + >>> a=PluginManager() + >>> a.x=6 + >>> b=PluginManager('check') + >>> print b.x + 6 + >>> b=PluginManager() # reset settings + >>> print b.x + + >>> b.x=7 + >>> print a.x + 7 + >>> a.y.z=8 + >>> print b.y.z + 8 + >>> test_thread_separation() + 5 + >>> plugins=PluginManager('me',db='mydb') + >>> print plugins.me.db + mydb + >>> print 'me' in plugins + True + >>> print plugins.me.installed + True - >>> a=PluginManager() - >>> a.x=6 - >>> b=PluginManager('check') - >>> print b.x - 6 - >>> b=PluginManager() # reset settings - >>> print b.x - - >>> b.x=7 - >>> print a.x - 7 - >>> a.y.z=8 - >>> print b.y.z - 8 - >>> test_thread_separation() - 5 - >>> plugins=PluginManager('me',db='mydb') - >>> print plugins.me.db - mydb - >>> print 'me' in plugins - True - >>> print plugins.me.installed - True """ instances = {} @@ -5166,21 +5166,23 @@ class PluginManager(object): class Expose(object): def __init__(self, base=None, basename=None, extensions=None, allow_download=True): """ - Usage: + Examples: + Use as:: - def static(): - return dict(files=Expose()) + def static(): + return dict(files=Expose()) - or + or:: - def static(): - path = os.path.join(request.folder,'static','public') - return dict(files=Expose(path,basename='public')) + def static(): + path = os.path.join(request.folder,'static','public') + return dict(files=Expose(path,basename='public')) + + Args: + extensions: an optional list of file extensions for filtering + displayed files: e.g. `['.py', '.jpg']` + allow_download: whether to allow downloading selected files - extensions: - an optional list of file extensions for filtering displayed files: - ['.py', '.jpg'] - allow_download: whether to allow downloading selected files """ current.session.forget() base = base or os.path.join(current.request.folder, 'static') @@ -5287,8 +5289,8 @@ class Wiki(object): @staticmethod def component(text): """ - In wiki docs allows @{component:controller/function/args} - which renders as a LOAD(..., ajax=True) + In wiki docs allows `@{component:controller/function/args}` + which renders as a `LOAD(..., ajax=True)` """ items = text.split('/') controller, function, args = items[0], items[1], items[2:] @@ -5323,16 +5325,18 @@ class Wiki(object): settings = self.settings = auth.settings.wiki - """render argument options: + """ + Args: + render: + - "markmin" - "html" - - - Sets a custom render function - - dict(html=, markmin=...): - dict(...) allows multiple custom render functions - - "multiple" - Is the same as {}. It enables per-record formats - using builtins + - `` : Sets a custom render function + - `dict(html=, markmin=...)`: dict(...) allows + multiple custom render functions + - "multiple" : Is the same as `{}`. It enables per-record + formats using builtins + """ engines = set(['markmin', 'html']) show_engine = False @@ -5353,7 +5357,7 @@ class Wiki(object): settings.function = function settings.groups = auth.user_groups.values() \ if groups is None else groups - + db = auth.db self.env = env or {} self.env['component'] = Wiki.component diff --git a/gluon/utf8.py b/gluon/utf8.py index e68cd354..0c9e4a54 100644 --- a/gluon/utf8.py +++ b/gluon/utf8.py @@ -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 -License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) - -Created by Vladyslav Kozlovskyy (Ukraine) - for Web2py project +| This file is part of the web2py Web Framework +| Copyrighted by Massimo Di Pierro +| License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) +| Created by Vladyslav Kozlovskyy (Ukraine) +| 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__': шовк Юляся яблуко + >>> 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'Ж'), diff --git a/gluon/utils.py b/gluon/utils.py index dbffeb4f..83d97cb0 100644 --- a/gluon/utils.py +++ b/gluon/utils.py @@ -2,11 +2,12 @@ # -*- coding: utf-8 -*- """ -This file is part of the web2py Web Framework -Copyrighted by Massimo Di Pierro -License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) +| This file is part of the web2py Web Framework +| Copyrighted by Massimo Di Pierro +| License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) This file specifically includes utilities for security. +-------------------------------------------------------- """ import threading @@ -65,7 +66,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 +76,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'): @@ -177,7 +178,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 +226,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 +244,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 +264,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'): diff --git a/gluon/validators.py b/gluon/validators.py index 62fee50c..ba98ae64 100644 --- a/gluon/validators.py +++ b/gluon/validators.py @@ -2,11 +2,13 @@ # -*- coding: utf-8 -*- """ -This file is part of the web2py Web Framework -Copyrighted by Massimo Di Pierro -License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) +| This file is part of the web2py Web Framework +| Copyrighted by Massimo Di Pierro +| License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) +| Thanks to ga2arch for help with IS_IN_DB and IS_NOT_IN_DB on GAE -Thanks to ga2arch for help with IS_IN_DB and IS_NOT_IN_DB on GAE +Validators +----------- """ import os @@ -21,7 +23,7 @@ import unicodedata from cStringIO import StringIO from gluon.utils import simple_hash, web2py_uuid, DIGEST_ALG_BY_SIZE from gluon.dal import FieldVirtual, FieldMethod - + regex_isint = re.compile('^[+-]?\d+$') JSONErrors = (NameError, TypeError, ValueError, AttributeError, @@ -144,11 +146,12 @@ class Validator(object): class IS_MATCH(Validator): """ - example:: + Example: + Used as:: - INPUT(_type='text', _name='name', requires=IS_MATCH('.+')) + INPUT(_type='text', _name='name', requires=IS_MATCH('.+')) - the argument of IS_MATCH is a regular expression:: + The argument of IS_MATCH is a regular expression:: >>> IS_MATCH('.+')('hello') ('hello', None) @@ -173,6 +176,7 @@ class IS_MATCH(Validator): >>> IS_MATCH('.+')('') ('', 'invalid expression') + """ def __init__(self, expression, error_message='Invalid expression', @@ -207,19 +211,21 @@ class IS_MATCH(Validator): class IS_EQUAL_TO(Validator): """ - example:: + Example: + Used as:: - INPUT(_type='text', _name='password') - INPUT(_type='text', _name='password2', - requires=IS_EQUAL_TO(request.vars.password)) + INPUT(_type='text', _name='password') + INPUT(_type='text', _name='password2', + requires=IS_EQUAL_TO(request.vars.password)) - the argument of IS_EQUAL_TO is a string + The argument of IS_EQUAL_TO is a string:: >>> IS_EQUAL_TO('aaa')('aaa') ('aaa', None) >>> IS_EQUAL_TO('aaa')('aab') ('aab', 'no match') + """ def __init__(self, expression, error_message='No match'): @@ -234,18 +240,20 @@ class IS_EQUAL_TO(Validator): class IS_EXPR(Validator): """ - example:: + Example: + Used as:: - INPUT(_type='text', _name='name', - requires=IS_EXPR('5 < int(value) < 10')) + INPUT(_type='text', _name='name', + requires=IS_EXPR('5 < int(value) < 10')) - the argument of IS_EXPR must be python condition:: + The argument of IS_EXPR must be python condition:: >>> IS_EXPR('int(value) < 2')('1') ('1', None) >>> IS_EXPR('int(value) < 2')('2') ('2', 'invalid expression') + """ def __init__(self, expression, error_message='Invalid expression', environment=None): @@ -269,30 +277,33 @@ class IS_LENGTH(Validator): Checks if length of field's value fits between given boundaries. Works for both text and file inputs. - Arguments: + Args: + maxsize: maximum allowed length / size + minsize: minimum allowed length / size - maxsize: maximum allowed length / size - minsize: minimum allowed length / size + Examples: + Check if text string is shorter than 33 characters:: - Examples:: + INPUT(_type='text', _name='name', requires=IS_LENGTH(32)) - #Check if text string is shorter than 33 characters: - INPUT(_type='text', _name='name', requires=IS_LENGTH(32)) + Check if password string is longer than 5 characters:: - #Check if password string is longer than 5 characters: - INPUT(_type='password', _name='name', requires=IS_LENGTH(minsize=6)) + INPUT(_type='password', _name='name', requires=IS_LENGTH(minsize=6)) - #Check if uploaded file has size between 1KB and 1MB: - INPUT(_type='file', _name='name', requires=IS_LENGTH(1048576, 1024)) + Check if uploaded file has size between 1KB and 1MB:: - >>> IS_LENGTH()('') - ('', None) - >>> IS_LENGTH()('1234567890') - ('1234567890', None) - >>> IS_LENGTH(maxsize=5, minsize=0)('1234567890') # too long - ('1234567890', 'enter from 0 to 5 characters') - >>> IS_LENGTH(maxsize=50, minsize=20)('1234567890') # too short - ('1234567890', 'enter from 20 to 50 characters') + INPUT(_type='file', _name='name', requires=IS_LENGTH(1048576, 1024)) + + Other examples:: + + >>> IS_LENGTH()('') + ('', None) + >>> IS_LENGTH()('1234567890') + ('1234567890', None) + >>> IS_LENGTH(maxsize=5, minsize=0)('1234567890') # too long + ('1234567890', 'enter from 0 to 5 characters') + >>> IS_LENGTH(maxsize=50, minsize=20)('1234567890') # too short + ('1234567890', 'enter from 20 to 50 characters') """ def __init__(self, maxsize=255, minsize=0, @@ -339,15 +350,17 @@ class IS_LENGTH(Validator): class IS_JSON(Validator): """ - example:: - INPUT(_type='text', _name='name', - requires=IS_JSON(error_message="This is not a valid json input") + Example: + Used as:: - >>> IS_JSON()('{"a": 100}') - ({u'a': 100}, None) + INPUT(_type='text', _name='name', + requires=IS_JSON(error_message="This is not a valid json input") - >>> IS_JSON()('spam1234') - ('spam1234', 'invalid json') + >>> IS_JSON()('{"a": 100}') + ({u'a': 100}, None) + + >>> IS_JSON()('spam1234') + ('spam1234', 'invalid json') """ def __init__(self, error_message='Invalid json', native_json=False): @@ -371,12 +384,13 @@ class IS_JSON(Validator): class IS_IN_SET(Validator): """ - example:: + Example: + Used as:: - INPUT(_type='text', _name='name', - requires=IS_IN_SET(['max', 'john'],zero='')) + INPUT(_type='text', _name='name', + requires=IS_IN_SET(['max', 'john'],zero='')) - the argument of IS_IN_SET must be a list or set + The argument of IS_IN_SET must be a list or set:: >>> IS_IN_SET(['max', 'john'])('max') ('max', None) @@ -395,6 +409,7 @@ class IS_IN_SET(Validator): ('1', None) >>> IS_IN_SET([('id1','first label'), ('id2','second label')])('id1') # Redundant way ('id1', None) + """ def __init__( @@ -463,10 +478,11 @@ regex2 = re.compile('%\(([^\)]+)\)\d*(?:\.\d+)?[a-zA-Z]') class IS_IN_DB(Validator): """ - example:: + Example: + Used as:: - INPUT(_type='text', _name='name', - requires=IS_IN_DB(db, db.mytable.myfield, zero='')) + INPUT(_type='text', _name='name', + requires=IS_IN_DB(db, db.mytable.myfield, zero='')) used for reference fields, rendered as a dropbox """ @@ -611,9 +627,10 @@ class IS_IN_DB(Validator): class IS_NOT_IN_DB(Validator): """ - example:: + Example: + Used as:: - INPUT(_type='text', _name='name', requires=IS_NOT_IN_DB(db, db.table)) + INPUT(_type='text', _name='name', requires=IS_NOT_IN_DB(db, db.table)) makes the field unique """ @@ -688,41 +705,42 @@ def range_error_message(error_message, what_to_enter, minimum, maximum): class IS_INT_IN_RANGE(Validator): """ - Determine that the argument is (or can be represented as) an int, + Determines that the argument is (or can be represented as) an int, and that it falls within the specified range. The range is interpreted in the Pythonic way, so the test is: min <= value < max. The minimum and maximum limits can be None, meaning no lower or upper limit, respectively. - example:: + Example: + Used as:: - INPUT(_type='text', _name='name', requires=IS_INT_IN_RANGE(0, 10)) + INPUT(_type='text', _name='name', requires=IS_INT_IN_RANGE(0, 10)) - >>> IS_INT_IN_RANGE(1,5)('4') - (4, None) - >>> IS_INT_IN_RANGE(1,5)(4) - (4, None) - >>> IS_INT_IN_RANGE(1,5)(1) - (1, None) - >>> IS_INT_IN_RANGE(1,5)(5) - (5, 'enter an integer between 1 and 4') - >>> IS_INT_IN_RANGE(1,5)(5) - (5, 'enter an integer between 1 and 4') - >>> IS_INT_IN_RANGE(1,5)(3.5) - (3.5, 'enter an integer between 1 and 4') - >>> IS_INT_IN_RANGE(None,5)('4') - (4, None) - >>> IS_INT_IN_RANGE(None,5)('6') - ('6', 'enter an integer less than or equal to 4') - >>> IS_INT_IN_RANGE(1,None)('4') - (4, None) - >>> IS_INT_IN_RANGE(1,None)('0') - ('0', 'enter an integer greater than or equal to 1') - >>> IS_INT_IN_RANGE()(6) - (6, None) - >>> IS_INT_IN_RANGE()('abc') - ('abc', 'enter an integer') + >>> IS_INT_IN_RANGE(1,5)('4') + (4, None) + >>> IS_INT_IN_RANGE(1,5)(4) + (4, None) + >>> IS_INT_IN_RANGE(1,5)(1) + (1, None) + >>> IS_INT_IN_RANGE(1,5)(5) + (5, 'enter an integer between 1 and 4') + >>> IS_INT_IN_RANGE(1,5)(5) + (5, 'enter an integer between 1 and 4') + >>> IS_INT_IN_RANGE(1,5)(3.5) + (3.5, 'enter an integer between 1 and 4') + >>> IS_INT_IN_RANGE(None,5)('4') + (4, None) + >>> IS_INT_IN_RANGE(None,5)('6') + ('6', 'enter an integer less than or equal to 4') + >>> IS_INT_IN_RANGE(1,None)('4') + (4, None) + >>> IS_INT_IN_RANGE(1,None)('0') + ('0', 'enter an integer greater than or equal to 1') + >>> IS_INT_IN_RANGE()(6) + (6, None) + >>> IS_INT_IN_RANGE()('abc') + ('abc', 'enter an integer') """ def __init__( @@ -756,41 +774,42 @@ def str2dec(number): class IS_FLOAT_IN_RANGE(Validator): """ - Determine that the argument is (or can be represented as) a float, + Determines that the argument is (or can be represented as) a float, and that it falls within the specified inclusive range. The comparison is made with native arithmetic. The minimum and maximum limits can be None, meaning no lower or upper limit, respectively. - example:: + Example: + Used as:: - INPUT(_type='text', _name='name', requires=IS_FLOAT_IN_RANGE(0, 10)) + INPUT(_type='text', _name='name', requires=IS_FLOAT_IN_RANGE(0, 10)) - >>> IS_FLOAT_IN_RANGE(1,5)('4') - (4.0, None) - >>> IS_FLOAT_IN_RANGE(1,5)(4) - (4.0, None) - >>> IS_FLOAT_IN_RANGE(1,5)(1) - (1.0, None) - >>> IS_FLOAT_IN_RANGE(1,5)(5.25) - (5.25, 'enter a number between 1 and 5') - >>> IS_FLOAT_IN_RANGE(1,5)(6.0) - (6.0, 'enter a number between 1 and 5') - >>> IS_FLOAT_IN_RANGE(1,5)(3.5) - (3.5, None) - >>> IS_FLOAT_IN_RANGE(1,None)(3.5) - (3.5, None) - >>> IS_FLOAT_IN_RANGE(None,5)(3.5) - (3.5, None) - >>> IS_FLOAT_IN_RANGE(1,None)(0.5) - (0.5, 'enter a number greater than or equal to 1') - >>> IS_FLOAT_IN_RANGE(None,5)(6.5) - (6.5, 'enter a number less than or equal to 5') - >>> IS_FLOAT_IN_RANGE()(6.5) - (6.5, None) - >>> IS_FLOAT_IN_RANGE()('abc') - ('abc', 'enter a number') + >>> IS_FLOAT_IN_RANGE(1,5)('4') + (4.0, None) + >>> IS_FLOAT_IN_RANGE(1,5)(4) + (4.0, None) + >>> IS_FLOAT_IN_RANGE(1,5)(1) + (1.0, None) + >>> IS_FLOAT_IN_RANGE(1,5)(5.25) + (5.25, 'enter a number between 1 and 5') + >>> IS_FLOAT_IN_RANGE(1,5)(6.0) + (6.0, 'enter a number between 1 and 5') + >>> IS_FLOAT_IN_RANGE(1,5)(3.5) + (3.5, None) + >>> IS_FLOAT_IN_RANGE(1,None)(3.5) + (3.5, None) + >>> IS_FLOAT_IN_RANGE(None,5)(3.5) + (3.5, None) + >>> IS_FLOAT_IN_RANGE(1,None)(0.5) + (0.5, 'enter a number greater than or equal to 1') + >>> IS_FLOAT_IN_RANGE(None,5)(6.5) + (6.5, 'enter a number less than or equal to 5') + >>> IS_FLOAT_IN_RANGE()(6.5) + (6.5, None) + >>> IS_FLOAT_IN_RANGE()('abc') + ('abc', 'enter a number') """ def __init__( @@ -827,55 +846,56 @@ class IS_FLOAT_IN_RANGE(Validator): class IS_DECIMAL_IN_RANGE(Validator): """ - Determine that the argument is (or can be represented as) a Python Decimal, + Determines that the argument is (or can be represented as) a Python Decimal, and that it falls within the specified inclusive range. The comparison is made with Python Decimal arithmetic. The minimum and maximum limits can be None, meaning no lower or upper limit, respectively. - example:: + Example: + Used as:: - INPUT(_type='text', _name='name', requires=IS_DECIMAL_IN_RANGE(0, 10)) + INPUT(_type='text', _name='name', requires=IS_DECIMAL_IN_RANGE(0, 10)) - >>> IS_DECIMAL_IN_RANGE(1,5)('4') - (Decimal('4'), None) - >>> IS_DECIMAL_IN_RANGE(1,5)(4) - (Decimal('4'), None) - >>> IS_DECIMAL_IN_RANGE(1,5)(1) - (Decimal('1'), None) - >>> IS_DECIMAL_IN_RANGE(1,5)(5.25) - (5.25, 'enter a number between 1 and 5') - >>> IS_DECIMAL_IN_RANGE(5.25,6)(5.25) - (Decimal('5.25'), None) - >>> IS_DECIMAL_IN_RANGE(5.25,6)('5.25') - (Decimal('5.25'), None) - >>> IS_DECIMAL_IN_RANGE(1,5)(6.0) - (6.0, 'enter a number between 1 and 5') - >>> IS_DECIMAL_IN_RANGE(1,5)(3.5) - (Decimal('3.5'), None) - >>> IS_DECIMAL_IN_RANGE(1.5,5.5)(3.5) - (Decimal('3.5'), None) - >>> IS_DECIMAL_IN_RANGE(1.5,5.5)(6.5) - (6.5, 'enter a number between 1.5 and 5.5') - >>> IS_DECIMAL_IN_RANGE(1.5,None)(6.5) - (Decimal('6.5'), None) - >>> IS_DECIMAL_IN_RANGE(1.5,None)(0.5) - (0.5, 'enter a number greater than or equal to 1.5') - >>> IS_DECIMAL_IN_RANGE(None,5.5)(4.5) - (Decimal('4.5'), None) - >>> IS_DECIMAL_IN_RANGE(None,5.5)(6.5) - (6.5, 'enter a number less than or equal to 5.5') - >>> IS_DECIMAL_IN_RANGE()(6.5) - (Decimal('6.5'), None) - >>> IS_DECIMAL_IN_RANGE(0,99)(123.123) - (123.123, 'enter a number between 0 and 99') - >>> IS_DECIMAL_IN_RANGE(0,99)('123.123') - ('123.123', 'enter a number between 0 and 99') - >>> IS_DECIMAL_IN_RANGE(0,99)('12.34') - (Decimal('12.34'), None) - >>> IS_DECIMAL_IN_RANGE()('abc') - ('abc', 'enter a number') + >>> IS_DECIMAL_IN_RANGE(1,5)('4') + (Decimal('4'), None) + >>> IS_DECIMAL_IN_RANGE(1,5)(4) + (Decimal('4'), None) + >>> IS_DECIMAL_IN_RANGE(1,5)(1) + (Decimal('1'), None) + >>> IS_DECIMAL_IN_RANGE(1,5)(5.25) + (5.25, 'enter a number between 1 and 5') + >>> IS_DECIMAL_IN_RANGE(5.25,6)(5.25) + (Decimal('5.25'), None) + >>> IS_DECIMAL_IN_RANGE(5.25,6)('5.25') + (Decimal('5.25'), None) + >>> IS_DECIMAL_IN_RANGE(1,5)(6.0) + (6.0, 'enter a number between 1 and 5') + >>> IS_DECIMAL_IN_RANGE(1,5)(3.5) + (Decimal('3.5'), None) + >>> IS_DECIMAL_IN_RANGE(1.5,5.5)(3.5) + (Decimal('3.5'), None) + >>> IS_DECIMAL_IN_RANGE(1.5,5.5)(6.5) + (6.5, 'enter a number between 1.5 and 5.5') + >>> IS_DECIMAL_IN_RANGE(1.5,None)(6.5) + (Decimal('6.5'), None) + >>> IS_DECIMAL_IN_RANGE(1.5,None)(0.5) + (0.5, 'enter a number greater than or equal to 1.5') + >>> IS_DECIMAL_IN_RANGE(None,5.5)(4.5) + (Decimal('4.5'), None) + >>> IS_DECIMAL_IN_RANGE(None,5.5)(6.5) + (6.5, 'enter a number less than or equal to 5.5') + >>> IS_DECIMAL_IN_RANGE()(6.5) + (Decimal('6.5'), None) + >>> IS_DECIMAL_IN_RANGE(0,99)(123.123) + (123.123, 'enter a number between 0 and 99') + >>> IS_DECIMAL_IN_RANGE(0,99)('123.123') + ('123.123', 'enter a number between 0 and 99') + >>> IS_DECIMAL_IN_RANGE(0,99)('12.34') + (Decimal('12.34'), None) + >>> IS_DECIMAL_IN_RANGE()('abc') + ('abc', 'enter a number') """ def __init__( @@ -923,34 +943,35 @@ def is_empty(value, empty_regex=None): class IS_NOT_EMPTY(Validator): """ - example:: + Example: + Used as:: - INPUT(_type='text', _name='name', requires=IS_NOT_EMPTY()) + INPUT(_type='text', _name='name', requires=IS_NOT_EMPTY()) - >>> IS_NOT_EMPTY()(1) - (1, None) - >>> IS_NOT_EMPTY()(0) - (0, None) - >>> IS_NOT_EMPTY()('x') - ('x', None) - >>> IS_NOT_EMPTY()(' x ') - ('x', None) - >>> IS_NOT_EMPTY()(None) - (None, 'enter a value') - >>> IS_NOT_EMPTY()('') - ('', 'enter a value') - >>> IS_NOT_EMPTY()(' ') - ('', 'enter a value') - >>> IS_NOT_EMPTY()(' \\n\\t') - ('', 'enter a value') - >>> IS_NOT_EMPTY()([]) - ([], 'enter a value') - >>> IS_NOT_EMPTY(empty_regex='def')('def') - ('', 'enter a value') - >>> IS_NOT_EMPTY(empty_regex='de[fg]')('deg') - ('', 'enter a value') - >>> IS_NOT_EMPTY(empty_regex='def')('abc') - ('abc', None) + >>> IS_NOT_EMPTY()(1) + (1, None) + >>> IS_NOT_EMPTY()(0) + (0, None) + >>> IS_NOT_EMPTY()('x') + ('x', None) + >>> IS_NOT_EMPTY()(' x ') + ('x', None) + >>> IS_NOT_EMPTY()(None) + (None, 'enter a value') + >>> IS_NOT_EMPTY()('') + ('', 'enter a value') + >>> IS_NOT_EMPTY()(' ') + ('', 'enter a value') + >>> IS_NOT_EMPTY()(' \\n\\t') + ('', 'enter a value') + >>> IS_NOT_EMPTY()([]) + ([], 'enter a value') + >>> IS_NOT_EMPTY(empty_regex='def')('def') + ('', 'enter a value') + >>> IS_NOT_EMPTY(empty_regex='de[fg]')('deg') + ('', 'enter a value') + >>> IS_NOT_EMPTY(empty_regex='def')('abc') + ('abc', None) """ def __init__(self, error_message='Enter a value', empty_regex=None): @@ -969,18 +990,19 @@ class IS_NOT_EMPTY(Validator): class IS_ALPHANUMERIC(IS_MATCH): """ - example:: + Example: + Used as:: - INPUT(_type='text', _name='name', requires=IS_ALPHANUMERIC()) + INPUT(_type='text', _name='name', requires=IS_ALPHANUMERIC()) - >>> IS_ALPHANUMERIC()('1') - ('1', None) - >>> IS_ALPHANUMERIC()('') - ('', None) - >>> IS_ALPHANUMERIC()('A_a') - ('A_a', None) - >>> IS_ALPHANUMERIC()('!') - ('!', 'enter only letters, numbers, and underscore') + >>> IS_ALPHANUMERIC()('1') + ('1', None) + >>> IS_ALPHANUMERIC()('') + ('', None) + >>> IS_ALPHANUMERIC()('A_a') + ('A_a', None) + >>> IS_ALPHANUMERIC()('!') + ('!', 'enter only letters, numbers, and underscore') """ def __init__(self, error_message='Enter only letters, numbers, and underscore'): @@ -997,81 +1019,82 @@ class IS_EMAIL(Validator): generally following the RFCs, except that we disallow quoted strings and permit underscores and leading numerics in subdomain labels - Arguments: - - - banned: regex text for disallowed address domains - - forced: regex text for required address domains + Args: + banned: regex text for disallowed address domains + forced: regex text for required address domains Both arguments can also be custom objects with a match(value) method. - Examples:: + Example: + Check for valid email address:: - #Check for valid email address: - INPUT(_type='text', _name='name', - requires=IS_EMAIL()) + INPUT(_type='text', _name='name', + requires=IS_EMAIL()) - #Check for valid email address that can't be from a .com domain: - INPUT(_type='text', _name='name', - requires=IS_EMAIL(banned='^.*\.com(|\..*)$')) + Check for valid email address that can't be from a .com domain:: - #Check for valid email address that must be from a .edu domain: - INPUT(_type='text', _name='name', - requires=IS_EMAIL(forced='^.*\.edu(|\..*)$')) + INPUT(_type='text', _name='name', + requires=IS_EMAIL(banned='^.*\.com(|\..*)$')) - >>> IS_EMAIL()('a@b.com') - ('a@b.com', None) - >>> IS_EMAIL()('abc@def.com') - ('abc@def.com', None) - >>> IS_EMAIL()('abc@3def.com') - ('abc@3def.com', None) - >>> IS_EMAIL()('abc@def.us') - ('abc@def.us', None) - >>> IS_EMAIL()('abc@d_-f.us') - ('abc@d_-f.us', None) - >>> IS_EMAIL()('@def.com') # missing name - ('@def.com', 'enter a valid email address') - >>> IS_EMAIL()('"abc@def".com') # quoted name - ('"abc@def".com', 'enter a valid email address') - >>> IS_EMAIL()('abc+def.com') # no @ - ('abc+def.com', 'enter a valid email address') - >>> IS_EMAIL()('abc@def.x') # one-char TLD - ('abc@def.x', 'enter a valid email address') - >>> IS_EMAIL()('abc@def.12') # numeric TLD - ('abc@def.12', 'enter a valid email address') - >>> IS_EMAIL()('abc@def..com') # double-dot in domain - ('abc@def..com', 'enter a valid email address') - >>> IS_EMAIL()('abc@.def.com') # dot starts domain - ('abc@.def.com', 'enter a valid email address') - >>> IS_EMAIL()('abc@def.c_m') # underscore in TLD - ('abc@def.c_m', 'enter a valid email address') - >>> IS_EMAIL()('NotAnEmail') # missing @ - ('NotAnEmail', 'enter a valid email address') - >>> IS_EMAIL()('abc@NotAnEmail') # missing TLD - ('abc@NotAnEmail', 'enter a valid email address') - >>> IS_EMAIL()('customer/department@example.com') - ('customer/department@example.com', None) - >>> IS_EMAIL()('$A12345@example.com') - ('$A12345@example.com', None) - >>> IS_EMAIL()('!def!xyz%abc@example.com') - ('!def!xyz%abc@example.com', None) - >>> IS_EMAIL()('_Yosemite.Sam@example.com') - ('_Yosemite.Sam@example.com', None) - >>> IS_EMAIL()('~@example.com') - ('~@example.com', None) - >>> IS_EMAIL()('.wooly@example.com') # dot starts name - ('.wooly@example.com', 'enter a valid email address') - >>> IS_EMAIL()('wo..oly@example.com') # adjacent dots in name - ('wo..oly@example.com', 'enter a valid email address') - >>> IS_EMAIL()('pootietang.@example.com') # dot ends name - ('pootietang.@example.com', 'enter a valid email address') - >>> IS_EMAIL()('.@example.com') # name is bare dot - ('.@example.com', 'enter a valid email address') - >>> IS_EMAIL()('Ima.Fool@example.com') - ('Ima.Fool@example.com', None) - >>> IS_EMAIL()('Ima Fool@example.com') # space in name - ('Ima Fool@example.com', 'enter a valid email address') - >>> IS_EMAIL()('localguy@localhost') # localhost as domain - ('localguy@localhost', None) + Check for valid email address that must be from a .edu domain:: + + INPUT(_type='text', _name='name', + requires=IS_EMAIL(forced='^.*\.edu(|\..*)$')) + + >>> IS_EMAIL()('a@b.com') + ('a@b.com', None) + >>> IS_EMAIL()('abc@def.com') + ('abc@def.com', None) + >>> IS_EMAIL()('abc@3def.com') + ('abc@3def.com', None) + >>> IS_EMAIL()('abc@def.us') + ('abc@def.us', None) + >>> IS_EMAIL()('abc@d_-f.us') + ('abc@d_-f.us', None) + >>> IS_EMAIL()('@def.com') # missing name + ('@def.com', 'enter a valid email address') + >>> IS_EMAIL()('"abc@def".com') # quoted name + ('"abc@def".com', 'enter a valid email address') + >>> IS_EMAIL()('abc+def.com') # no @ + ('abc+def.com', 'enter a valid email address') + >>> IS_EMAIL()('abc@def.x') # one-char TLD + ('abc@def.x', 'enter a valid email address') + >>> IS_EMAIL()('abc@def.12') # numeric TLD + ('abc@def.12', 'enter a valid email address') + >>> IS_EMAIL()('abc@def..com') # double-dot in domain + ('abc@def..com', 'enter a valid email address') + >>> IS_EMAIL()('abc@.def.com') # dot starts domain + ('abc@.def.com', 'enter a valid email address') + >>> IS_EMAIL()('abc@def.c_m') # underscore in TLD + ('abc@def.c_m', 'enter a valid email address') + >>> IS_EMAIL()('NotAnEmail') # missing @ + ('NotAnEmail', 'enter a valid email address') + >>> IS_EMAIL()('abc@NotAnEmail') # missing TLD + ('abc@NotAnEmail', 'enter a valid email address') + >>> IS_EMAIL()('customer/department@example.com') + ('customer/department@example.com', None) + >>> IS_EMAIL()('$A12345@example.com') + ('$A12345@example.com', None) + >>> IS_EMAIL()('!def!xyz%abc@example.com') + ('!def!xyz%abc@example.com', None) + >>> IS_EMAIL()('_Yosemite.Sam@example.com') + ('_Yosemite.Sam@example.com', None) + >>> IS_EMAIL()('~@example.com') + ('~@example.com', None) + >>> IS_EMAIL()('.wooly@example.com') # dot starts name + ('.wooly@example.com', 'enter a valid email address') + >>> IS_EMAIL()('wo..oly@example.com') # adjacent dots in name + ('wo..oly@example.com', 'enter a valid email address') + >>> IS_EMAIL()('pootietang.@example.com') # dot ends name + ('pootietang.@example.com', 'enter a valid email address') + >>> IS_EMAIL()('.@example.com') # name is bare dot + ('.@example.com', 'enter a valid email address') + >>> IS_EMAIL()('Ima.Fool@example.com') + ('Ima.Fool@example.com', None) + >>> IS_EMAIL()('Ima Fool@example.com') # space in name + ('Ima Fool@example.com', 'enter a valid email address') + >>> IS_EMAIL()('localguy@localhost') # localhost as domain + ('localguy@localhost', None) """ @@ -1125,13 +1148,15 @@ class IS_EMAIL(Validator): class IS_LIST_OF_EMAILS(object): """ - use as follows: - Field('emails','list:string', - widget=SQLFORM.widgets.text.widget, - requires=IS_LIST_OF_EMAILS(), - represent=lambda v,r: \ - SPAN(*[A(x,_href='mailto:'+x) for x in (v or [])]) - ) + Example: + Used as:: + + Field('emails','list:string', + widget=SQLFORM.widgets.text.widget, + requires=IS_LIST_OF_EMAILS(), + represent=lambda v,r: \ + SPAN(*[A(x,_href='mailto:'+x) for x in (v or [])]) + ) """ split_emails = re.compile('[^,;\s]+') def __init__(self, error_message = 'Invalid emails: %s'): @@ -1311,10 +1336,12 @@ def escape_unicode(string): converted into a URL escaped form based on its hexadecimal value. For example, the unicode character '\u4e86' will become the string '%4e%86' - :param string: unicode string, the unicode string to convert into an - escaped US-ASCII form - :returns: the US-ASCII escaped form of the inputted string - :rtype: string + Args: + string: unicode string, the unicode string to convert into an + escaped US-ASCII form + + Returns: + string: the US-ASCII escaped form of the inputted string @author: Jonathan Benn ''' @@ -1338,13 +1365,17 @@ def unicode_to_ascii_authority(authority): For example, u'www.Alliancefran\xe7aise.nu' will be converted into 'www.xn--alliancefranaise-npb.nu' - :param authority: unicode string, the URL authority component to convert, - e.g. u'www.Alliancefran\xe7aise.nu' - :returns: the US-ASCII character equivalent to the inputed authority, + Args: + authority: unicode string, the URL authority component to convert, + e.g. u'www.Alliancefran\xe7aise.nu' + + Returns: + string: the US-ASCII character equivalent to the inputed authority, e.g. 'www.xn--alliancefranaise-npb.nu' - :rtype: string - :raises Exception: if the function is not able to convert the inputed - authority + + Raises: + Exception: if the function is not able to convert the inputed + authority @author: Jonathan Benn ''' @@ -1398,12 +1429,14 @@ def unicode_to_ascii_url(url, prepend_scheme): converted into '%4E%2D'. Testing with Firefox v3.0.5 has shown that it can understand this kind of URI encoding. - :param url: unicode string, the URL to convert from unicode into US-ASCII - :param prepend_scheme: string, a protocol scheme to prepend to the URL if - we're having trouble parsing it. - e.g. "http". Input None to disable this functionality - :returns: a US-ASCII equivalent of the inputed url - :rtype: string + Args: + url: unicode string, the URL to convert from unicode into US-ASCII + prepend_scheme: string, a protocol scheme to prepend to the URL if + we're having trouble parsing it. + e.g. "http". Input None to disable this functionality + + Returns: + string: a US-ASCII equivalent of the inputed url @author: Jonathan Benn ''' @@ -1464,8 +1497,16 @@ class IS_GENERIC_URL(Validator): @author: Jonathan Benn - >>> IS_GENERIC_URL()('http://user@abc.com') - ('http://user@abc.com', None) + >>> IS_GENERIC_URL()('http://user@abc.com') + ('http://user@abc.com', None) + + Args: + error_message: a string, the error message to give the end user + if the URL does not validate + allowed_schemes: a list containing strings or None. Each element + is a scheme the inputed URL is allowed to use + prepend_scheme: a string, this scheme is prepended if it's + necessary to make the URL valid """ @@ -1475,14 +1516,6 @@ class IS_GENERIC_URL(Validator): allowed_schemes=None, prepend_scheme=None, ): - """ - :param error_message: a string, the error message to give the end user - if the URL does not validate - :param allowed_schemes: a list containing strings or None. Each element - is a scheme the inputed URL is allowed to use - :param prepend_scheme: a string, this scheme is prepended if it's - necessary to make the URL valid - """ self.error_message = error_message if allowed_schemes is None: @@ -1499,8 +1532,11 @@ class IS_GENERIC_URL(Validator): def __call__(self, value): """ - :param value: a string, the URL to validate - :returns: a tuple, where tuple[0] is the inputed value (possible + Args: + value: a string, the URL to validate + + Returns: + a tuple, where tuple[0] is the inputed value (possible prepended with prepend_scheme), and tuple[1] is either None (success!) or the string error_message """ @@ -1866,21 +1902,28 @@ class IS_HTTP_URL(Validator): @author: Jonathan Benn - >>> IS_HTTP_URL()('http://1.2.3.4') - ('http://1.2.3.4', None) - >>> IS_HTTP_URL()('http://abc.com') - ('http://abc.com', None) - >>> IS_HTTP_URL()('https://abc.com') - ('https://abc.com', None) - >>> IS_HTTP_URL()('httpx://abc.com') - ('httpx://abc.com', 'enter a valid URL') - >>> IS_HTTP_URL()('http://abc.com:80') - ('http://abc.com:80', None) - >>> IS_HTTP_URL()('http://user@abc.com') - ('http://user@abc.com', None) - >>> IS_HTTP_URL()('http://user@1.2.3.4') - ('http://user@1.2.3.4', None) + >>> IS_HTTP_URL()('http://1.2.3.4') + ('http://1.2.3.4', None) + >>> IS_HTTP_URL()('http://abc.com') + ('http://abc.com', None) + >>> IS_HTTP_URL()('https://abc.com') + ('https://abc.com', None) + >>> IS_HTTP_URL()('httpx://abc.com') + ('httpx://abc.com', 'enter a valid URL') + >>> IS_HTTP_URL()('http://abc.com:80') + ('http://abc.com:80', None) + >>> IS_HTTP_URL()('http://user@abc.com') + ('http://user@abc.com', None) + >>> IS_HTTP_URL()('http://user@1.2.3.4') + ('http://user@1.2.3.4', None) + Args: + error_message: a string, the error message to give the end user + if the URL does not validate + allowed_schemes: a list containing strings or None. Each element + is a scheme the inputed URL is allowed to use + prepend_scheme: a string, this scheme is prepended if it's + necessary to make the URL valid """ GENERIC_VALID_IP = re.compile( @@ -1893,14 +1936,6 @@ class IS_HTTP_URL(Validator): allowed_schemes=None, prepend_scheme='http', ): - """ - :param error_message: a string, the error message to give the end user - if the URL does not validate - :param allowed_schemes: a list containing strings or None. Each element - is a scheme the inputed URL is allowed to use - :param prepend_scheme: a string, this scheme is prepended if it's - necessary to make the URL valid - """ self.error_message = error_message if allowed_schemes is None: @@ -1920,8 +1955,11 @@ class IS_HTTP_URL(Validator): def __call__(self, value): """ - :param value: a string, the URL to validate - :returns: a tuple, where tuple[0] is the inputed value + Args: + value: a string, the URL to validate + + Returns: + a tuple, where tuple[0] is the inputed value (possible prepended with prepend_scheme), and tuple[1] is either None (success!) or the string error_message """ @@ -1984,6 +2022,7 @@ class IS_HTTP_URL(Validator): class IS_URL(Validator): """ Rejects a URL string if any of the following is true: + * The string is empty or None * The string uses characters that are not allowed in a URL * The string breaks any of the HTTP syntactic rules @@ -1999,6 +2038,7 @@ class IS_URL(Validator): If the parameter mode='generic' is used, then this function's behavior changes. It then rejects a URL string if any of the following is true: + * The string is empty or None * The string uses characters that are not allowed in a URL * The URL scheme specified (if one is specified) is not valid @@ -2025,6 +2065,14 @@ class IS_URL(Validator): be escaped using the standard '%20' type syntax. e.g. the unicode character with hex code 0x4e86 will become '%4e%86' + Args: + error_message: a string, the error message to give the end user + if the URL does not validate + allowed_schemes: a list containing strings or None. Each element + is a scheme the inputed URL is allowed to use + prepend_scheme: a string, this scheme is prepended if it's + necessary to make the URL valid + Code Examples:: INPUT(_type='text', _name='name', requires=IS_URL()) @@ -2063,14 +2111,6 @@ class IS_URL(Validator): allowed_schemes=None, prepend_scheme='http', ): - """ - :param error_message: a string, the error message to give the end user - if the URL does not validate - :param allowed_schemes: a list containing strings or None. Each element - is a scheme the inputed URL is allowed to use - :param prepend_scheme: a string, this scheme is prepended if it's - necessary to make the URL valid - """ self.error_message = error_message self.mode = mode.lower() @@ -2090,8 +2130,11 @@ class IS_URL(Validator): def __call__(self, value): """ - :param value: a unicode or regular string, the URL to validate - :returns: a (string, string) tuple, where tuple[0] is the modified + Args: + value: a unicode or regular string, the URL to validate + + Returns: + a (string, string) tuple, where tuple[0] is the modified input value and tuple[1] is either None (success!) or the string error_message. The input value will never be modified in the case of an error. However, if there is success then the input URL @@ -2135,16 +2178,17 @@ regex_time = re.compile( class IS_TIME(Validator): """ - example:: + Example: + Use as:: - INPUT(_type='text', _name='name', requires=IS_TIME()) + INPUT(_type='text', _name='name', requires=IS_TIME()) understands the following formats hh:mm:ss [am/pm] hh:mm [am/pm] hh [am/pm] - [am/pm] is optional, ':' can be replaced by any other non-space non-digit + [am/pm] is optional, ':' can be replaced by any other non-space non-digit:: >>> IS_TIME()('21:30') (datetime.time(21, 30), None) @@ -2171,7 +2215,8 @@ class IS_TIME(Validator): >>> IS_TIME()('21:30::') ('21:30::', 'enter time as hh:mm:ss (seconds, am, pm optional)') >>> IS_TIME()('') - ('', 'enter time as hh:mm:ss (seconds, am, pm optional)') + ('', 'enter time as hh:mm:ss (seconds, am, pm optional)')ù + """ def __init__(self, error_message='Enter time as hh:mm:ss (seconds, am, pm optional)'): @@ -2189,7 +2234,7 @@ class IS_TIME(Validator): if value.group('d') == 'pm' and 0 < h < 12: h = h + 12 if value.group('d') == 'am' and h == 12: - h = 0 + h = 0 if not (h in range(24) and m in range(60) and s in range(60)): raise ValueError('Hours or minutes or seconds are outside of allowed range') @@ -2215,19 +2260,18 @@ utc = UTC() class IS_DATE(Validator): """ - example:: + Examples: + Use as:: - INPUT(_type='text', _name='name', requires=IS_DATE()) + INPUT(_type='text', _name='name', requires=IS_DATE()) date has to be in the ISO8960 format YYYY-MM-DD + timezome must be None or a pytz.timezone("America/Chicago") object """ def __init__(self, format='%Y-%m-%d', error_message='Enter date as %(format)s', timezone = None): - """ - timezome must be None or a pytz.timezone("America/Chicago") object - """ self.format = translate(format) self.error_message = str(error_message) self.timezone = timezone @@ -2270,11 +2314,13 @@ class IS_DATE(Validator): class IS_DATETIME(Validator): """ - example:: + Examples: + Use as:: - INPUT(_type='text', _name='name', requires=IS_DATETIME()) + INPUT(_type='text', _name='name', requires=IS_DATETIME()) datetime has to be in the ISO8960 format YYYY-MM-DD hh:mm:ss + timezome must be None or a pytz.timezone("America/Chicago") object """ isodatetime = '%Y-%m-%d %H:%M:%S' @@ -2299,9 +2345,6 @@ class IS_DATETIME(Validator): def __init__(self, format='%Y-%m-%d %H:%M:%S', error_message='Enter date and time as %(format)s', timezone=None): - """ - timezome must be None or a pytz.timezone("America/Chicago") object - """ self.format = translate(format) self.error_message = str(error_message) self.extremes = {} @@ -2341,23 +2384,24 @@ class IS_DATETIME(Validator): class IS_DATE_IN_RANGE(IS_DATE): """ - example:: + Examples: + Use as:: - >>> v = IS_DATE_IN_RANGE(minimum=datetime.date(2008,1,1), \ - maximum=datetime.date(2009,12,31), \ - format="%m/%d/%Y",error_message="Oops") + >>> v = IS_DATE_IN_RANGE(minimum=datetime.date(2008,1,1), \ + maximum=datetime.date(2009,12,31), \ + format="%m/%d/%Y",error_message="Oops") - >>> v('03/03/2008') - (datetime.date(2008, 3, 3), None) + >>> v('03/03/2008') + (datetime.date(2008, 3, 3), None) - >>> v('03/03/2010') - ('03/03/2010', 'oops') + >>> v('03/03/2010') + ('03/03/2010', 'oops') - >>> v(datetime.date(2008,3,3)) - (datetime.date(2008, 3, 3), None) + >>> v(datetime.date(2008,3,3)) + (datetime.date(2008, 3, 3), None) - >>> v(datetime.date(2010,3,3)) - (datetime.date(2010, 3, 3), 'oops') + >>> v(datetime.date(2010,3,3)) + (datetime.date(2010, 3, 3), 'oops') """ def __init__(self, @@ -2396,23 +2440,24 @@ class IS_DATE_IN_RANGE(IS_DATE): class IS_DATETIME_IN_RANGE(IS_DATETIME): """ - example:: + Examples: + Use as:: + >>> v = IS_DATETIME_IN_RANGE(\ + minimum=datetime.datetime(2008,1,1,12,20), \ + maximum=datetime.datetime(2009,12,31,12,20), \ + format="%m/%d/%Y %H:%M",error_message="Oops") + >>> v('03/03/2008 12:40') + (datetime.datetime(2008, 3, 3, 12, 40), None) - >>> v = IS_DATETIME_IN_RANGE(\ - minimum=datetime.datetime(2008,1,1,12,20), \ - maximum=datetime.datetime(2009,12,31,12,20), \ - format="%m/%d/%Y %H:%M",error_message="Oops") - >>> v('03/03/2008 12:40') - (datetime.datetime(2008, 3, 3, 12, 40), None) + >>> v('03/03/2010 10:34') + ('03/03/2010 10:34', 'oops') - >>> v('03/03/2010 10:34') - ('03/03/2010 10:34', 'oops') + >>> v(datetime.datetime(2008,3,3,0,0)) + (datetime.datetime(2008, 3, 3, 0, 0), None) - >>> v(datetime.datetime(2008,3,3,0,0)) - (datetime.datetime(2008, 3, 3, 0, 0), None) + >>> v(datetime.datetime(2010,3,3,0,0)) + (datetime.datetime(2010, 3, 3, 0, 0), 'oops') - >>> v(datetime.datetime(2010,3,3,0,0)) - (datetime.datetime(2010, 3, 3, 0, 0), 'oops') """ def __init__(self, minimum=None, @@ -2469,7 +2514,7 @@ class IS_LIST_OF(Validator): other = self.other if self.other: if not isinstance(other, (list,tuple)): - other = [other] + other = [other] for item in ivalue: if item.strip(): v = item @@ -2484,12 +2529,13 @@ class IS_LIST_OF(Validator): class IS_LOWER(Validator): """ - convert to lower case + Converts to lower case:: + + >>> IS_LOWER()('ABC') + ('abc', None) + >>> IS_LOWER()('Ñ') + ('\\xc3\\xb1', None) - >>> IS_LOWER()('ABC') - ('abc', None) - >>> IS_LOWER()('Ñ') - ('\\xc3\\xb1', None) """ def __call__(self, value): @@ -2498,12 +2544,13 @@ class IS_LOWER(Validator): class IS_UPPER(Validator): """ - convert to upper case + Converts to upper case:: + + >>> IS_UPPER()('abc') + ('ABC', None) + >>> IS_UPPER()('ñ') + ('\\xc3\\x91', None) - >>> IS_UPPER()('abc') - ('ABC', None) - >>> IS_UPPER()('ñ') - ('\\xc3\\x91', None) """ def __call__(self, value): @@ -2512,7 +2559,7 @@ class IS_UPPER(Validator): def urlify(s, maxlen=80, keep_underscores=False): """ - Convert incoming string to a simplified ASCII subset. + Converts incoming string to a simplified ASCII subset. if (keep_underscores): underscores are retained in the string else: underscores are translated to hyphens (default) """ @@ -2536,46 +2583,46 @@ def urlify(s, maxlen=80, keep_underscores=False): class IS_SLUG(Validator): """ - convert arbitrary text string to a slug + converts arbitrary text string to a slug:: - >>> IS_SLUG()('abc123') - ('abc123', None) - >>> IS_SLUG()('ABC123') - ('abc123', None) - >>> IS_SLUG()('abc-123') - ('abc-123', None) - >>> IS_SLUG()('abc--123') - ('abc-123', None) - >>> IS_SLUG()('abc 123') - ('abc-123', None) - >>> IS_SLUG()('abc\t_123') - ('abc-123', None) - >>> IS_SLUG()('-abc-') - ('abc', None) - >>> IS_SLUG()('--a--b--_ -c--') - ('a-b-c', None) - >>> IS_SLUG()('abc&123') - ('abc123', None) - >>> IS_SLUG()('abc&123&def') - ('abc123def', None) - >>> IS_SLUG()('ñ') - ('n', None) - >>> IS_SLUG(maxlen=4)('abc123') - ('abc1', None) - >>> IS_SLUG()('abc_123') - ('abc-123', None) - >>> IS_SLUG(keep_underscores=False)('abc_123') - ('abc-123', None) - >>> IS_SLUG(keep_underscores=True)('abc_123') - ('abc_123', None) - >>> IS_SLUG(check=False)('abc') - ('abc', None) - >>> IS_SLUG(check=True)('abc') - ('abc', None) - >>> IS_SLUG(check=False)('a bc') - ('a-bc', None) - >>> IS_SLUG(check=True)('a bc') - ('a bc', 'must be slug') + >>> IS_SLUG()('abc123') + ('abc123', None) + >>> IS_SLUG()('ABC123') + ('abc123', None) + >>> IS_SLUG()('abc-123') + ('abc-123', None) + >>> IS_SLUG()('abc--123') + ('abc-123', None) + >>> IS_SLUG()('abc 123') + ('abc-123', None) + >>> IS_SLUG()('abc\t_123') + ('abc-123', None) + >>> IS_SLUG()('-abc-') + ('abc', None) + >>> IS_SLUG()('--a--b--_ -c--') + ('a-b-c', None) + >>> IS_SLUG()('abc&123') + ('abc123', None) + >>> IS_SLUG()('abc&123&def') + ('abc123def', None) + >>> IS_SLUG()('ñ') + ('n', None) + >>> IS_SLUG(maxlen=4)('abc123') + ('abc1', None) + >>> IS_SLUG()('abc_123') + ('abc-123', None) + >>> IS_SLUG(keep_underscores=False)('abc_123') + ('abc-123', None) + >>> IS_SLUG(keep_underscores=True)('abc_123') + ('abc_123', None) + >>> IS_SLUG(check=False)('abc') + ('abc', None) + >>> IS_SLUG(check=True)('abc') + ('abc', None) + >>> IS_SLUG(check=False)('a bc') + ('a-bc', None) + >>> IS_SLUG(check=True)('a bc') + ('a bc', 'must be slug') """ @staticmethod @@ -2596,16 +2643,17 @@ class IS_SLUG(Validator): class ANY_OF(Validator): """ - test if any of the validators in a list return successfully + Tests if any of the validators in a list returns successfully:: + + >>> ANY_OF([IS_EMAIL(),IS_ALPHANUMERIC()])('a@b.co') + ('a@b.co', None) + >>> ANY_OF([IS_EMAIL(),IS_ALPHANUMERIC()])('abco') + ('abco', None) + >>> ANY_OF([IS_EMAIL(),IS_ALPHANUMERIC()])('@ab.co') + ('@ab.co', 'enter only letters, numbers, and underscore') + >>> ANY_OF([IS_ALPHANUMERIC(),IS_EMAIL()])('@ab.co') + ('@ab.co', 'enter a valid email address') - >>> ANY_OF([IS_EMAIL(),IS_ALPHANUMERIC()])('a@b.co') - ('a@b.co', None) - >>> ANY_OF([IS_EMAIL(),IS_ALPHANUMERIC()])('abco') - ('abco', None) - >>> ANY_OF([IS_EMAIL(),IS_ALPHANUMERIC()])('@ab.co') - ('@ab.co', 'enter only letters, numbers, and underscore') - >>> ANY_OF([IS_ALPHANUMERIC(),IS_EMAIL()])('@ab.co') - ('@ab.co', 'enter a valid email address') """ def __init__(self, subs): @@ -2628,20 +2676,20 @@ class ANY_OF(Validator): class IS_EMPTY_OR(Validator): """ - dummy class for testing IS_EMPTY_OR + Dummy class for testing IS_EMPTY_OR:: - >>> IS_EMPTY_OR(IS_EMAIL())('abc@def.com') - ('abc@def.com', None) - >>> IS_EMPTY_OR(IS_EMAIL())(' ') - (None, None) - >>> IS_EMPTY_OR(IS_EMAIL(), null='abc')(' ') - ('abc', None) - >>> IS_EMPTY_OR(IS_EMAIL(), null='abc', empty_regex='def')('def') - ('abc', None) - >>> IS_EMPTY_OR(IS_EMAIL())('abc') - ('abc', 'enter a valid email address') - >>> IS_EMPTY_OR(IS_EMAIL())(' abc ') - ('abc', 'enter a valid email address') + >>> IS_EMPTY_OR(IS_EMAIL())('abc@def.com') + ('abc@def.com', None) + >>> IS_EMPTY_OR(IS_EMAIL())(' ') + (None, None) + >>> IS_EMPTY_OR(IS_EMAIL(), null='abc')(' ') + ('abc', None) + >>> IS_EMPTY_OR(IS_EMAIL(), null='abc', empty_regex='def')('def') + ('abc', None) + >>> IS_EMPTY_OR(IS_EMAIL())('abc') + ('abc', 'enter a valid email address') + >>> IS_EMPTY_OR(IS_EMAIL())(' abc ') + ('abc', 'enter a valid email address') """ def __init__(self, other, null=None, empty_regex=None): @@ -2694,9 +2742,10 @@ IS_NULL_OR = IS_EMPTY_OR # for backward compatibility class CLEANUP(Validator): """ - example:: + Examples: + Use as:: - INPUT(_type='text', _name='name', requires=CLEANUP()) + INPUT(_type='text', _name='name', requires=CLEANUP()) removes special characters on validation """ @@ -2800,9 +2849,10 @@ class LazyCrypt(object): class CRYPT(object): """ - example:: + Examples: + Use as:: - INPUT(_type='text', _name='name', requires=CRYPT()) + INPUT(_type='text', _name='name', requires=CRYPT()) encodes the value on validation with a digest. @@ -2834,47 +2884,47 @@ class CRYPT(object): Supports standard algorithms - >>> for alg in ('md5','sha1','sha256','sha384','sha512'): - ... print str(CRYPT(digest_alg=alg,salt=True)('test')[0]) - md5$...$... - sha1$...$... - sha256$...$... - sha384$...$... - sha512$...$... + >>> for alg in ('md5','sha1','sha256','sha384','sha512'): + ... print str(CRYPT(digest_alg=alg,salt=True)('test')[0]) + md5$...$... + sha1$...$... + sha256$...$... + sha384$...$... + sha512$...$... The syntax is always alg$salt$hash Supports for pbkdf2 - >>> alg = 'pbkdf2(1000,20,sha512)' - >>> print str(CRYPT(digest_alg=alg,salt=True)('test')[0]) - pbkdf2(1000,20,sha512)$...$... + >>> alg = 'pbkdf2(1000,20,sha512)' + >>> print str(CRYPT(digest_alg=alg,salt=True)('test')[0]) + pbkdf2(1000,20,sha512)$...$... An optional hmac_key can be specified and it is used as salt prefix - >>> a = str(CRYPT(digest_alg='md5',key='mykey',salt=True)('test')[0]) - >>> print a - md5$...$... + >>> a = str(CRYPT(digest_alg='md5',key='mykey',salt=True)('test')[0]) + >>> print a + md5$...$... Even if the algorithm changes the hash can still be validated - >>> CRYPT(digest_alg='sha1',key='mykey',salt=True)('test')[0] == a - True + >>> CRYPT(digest_alg='sha1',key='mykey',salt=True)('test')[0] == a + True If no salt is specified CRYPT can guess the algorithms from length: - >>> a = str(CRYPT(digest_alg='sha1',salt=False)('test')[0]) - >>> a - 'sha1$$a94a8fe5ccb19ba61c4c0873d391e987982fbbd3' - >>> CRYPT(digest_alg='sha1',salt=False)('test')[0] == a - True - >>> CRYPT(digest_alg='sha1',salt=False)('test')[0] == a[6:] - True - >>> CRYPT(digest_alg='md5',salt=False)('test')[0] == a - True - >>> CRYPT(digest_alg='md5',salt=False)('test')[0] == a[6:] - True - """ + >>> a = str(CRYPT(digest_alg='sha1',salt=False)('test')[0]) + >>> a + 'sha1$$a94a8fe5ccb19ba61c4c0873d391e987982fbbd3' + >>> CRYPT(digest_alg='sha1',salt=False)('test')[0] == a + True + >>> CRYPT(digest_alg='sha1',salt=False)('test')[0] == a[6:] + True + >>> CRYPT(digest_alg='md5',salt=False)('test')[0] == a + True + >>> CRYPT(digest_alg='md5',salt=False)('test')[0] == a[6:] + True + """ def __init__(self, key=None, @@ -2914,7 +2964,7 @@ otherset = frozenset( def calc_entropy(string): - " calculate a simple entropy for a given string " + " calculates a simple entropy for a given string " import math alphabet = 0 # alphabet size other = set() @@ -2946,32 +2996,33 @@ def calc_entropy(string): class IS_STRONG(object): """ - example:: + Examples: + Use as:: - INPUT(_type='password', _name='passwd', + INPUT(_type='password', _name='passwd', requires=IS_STRONG(min=10, special=2, upper=2)) enforces complexity requirements on a field - >>> IS_STRONG(es=True)('Abcd1234') - ('Abcd1234', - 'Must include at least 1 of the following: ~!@#$%^&*()_+-=?<>,.:;{}[]|') - >>> IS_STRONG(es=True)('Abcd1234!') - ('Abcd1234!', None) - >>> IS_STRONG(es=True, entropy=1)('a') - ('a', None) - >>> IS_STRONG(es=True, entropy=1, min=2)('a') - ('a', 'Minimum length is 2') - >>> IS_STRONG(es=True, entropy=100)('abc123') - ('abc123', 'Entropy (32.35) less than required (100)') - >>> IS_STRONG(es=True, entropy=100)('and') - ('and', 'Entropy (14.57) less than required (100)') - >>> IS_STRONG(es=True, entropy=100)('aaa') - ('aaa', 'Entropy (14.42) less than required (100)') - >>> IS_STRONG(es=True, entropy=100)('a1d') - ('a1d', 'Entropy (15.97) less than required (100)') - >>> IS_STRONG(es=True, entropy=100)('añd') - ('a\\xc3\\xb1d', 'Entropy (18.13) less than required (100)') + >>> IS_STRONG(es=True)('Abcd1234') + ('Abcd1234', + 'Must include at least 1 of the following: ~!@#$%^&*()_+-=?<>,.:;{}[]|') + >>> IS_STRONG(es=True)('Abcd1234!') + ('Abcd1234!', None) + >>> IS_STRONG(es=True, entropy=1)('a') + ('a', None) + >>> IS_STRONG(es=True, entropy=1, min=2)('a') + ('a', 'Minimum length is 2') + >>> IS_STRONG(es=True, entropy=100)('abc123') + ('abc123', 'Entropy (32.35) less than required (100)') + >>> IS_STRONG(es=True, entropy=100)('and') + ('and', 'Entropy (14.57) less than required (100)') + >>> IS_STRONG(es=True, entropy=100)('aaa') + ('aaa', 'Entropy (14.42) less than required (100)') + >>> IS_STRONG(es=True, entropy=100)('a1d') + ('a1d', 'Entropy (15.97) less than required (100)') + >>> IS_STRONG(es=True, entropy=100)('añd') + ('a\\xc3\\xb1d', 'Entropy (18.13) less than required (100)') """ @@ -3098,27 +3149,28 @@ class IS_IMAGE(Validator): Code parts taken from http://mail.python.org/pipermail/python-list/2007-June/617126.html - Arguments: - - extensions: iterable containing allowed *lowercase* image file extensions - ('jpg' extension of uploaded file counts as 'jpeg') - maxsize: iterable containing maximum width and height of the image - minsize: iterable containing minimum width and height of the image + Args: + extensions: iterable containing allowed *lowercase* image file extensions + ('jpg' extension of uploaded file counts as 'jpeg') + maxsize: iterable containing maximum width and height of the image + minsize: iterable containing minimum width and height of the image Use (-1, -1) as minsize to pass image size check. - Examples:: + Examples: + Check if uploaded file is in any of supported image formats: - #Check if uploaded file is in any of supported image formats: - INPUT(_type='file', _name='name', requires=IS_IMAGE()) + INPUT(_type='file', _name='name', requires=IS_IMAGE()) - #Check if uploaded file is either JPEG or PNG: - INPUT(_type='file', _name='name', - requires=IS_IMAGE(extensions=('jpeg', 'png'))) + Check if uploaded file is either JPEG or PNG: - #Check if uploaded file is PNG with maximum size of 200x200 pixels: - INPUT(_type='file', _name='name', - requires=IS_IMAGE(extensions=('png'), maxsize=(200, 200))) + INPUT(_type='file', _name='name', + requires=IS_IMAGE(extensions=('jpeg', 'png'))) + + Check if uploaded file is PNG with maximum size of 200x200 pixels: + + INPUT(_type='file', _name='name', + requires=IS_IMAGE(extensions=('png'), maxsize=(200, 200))) """ def __init__(self, @@ -3200,35 +3252,37 @@ class IS_UPLOAD_FILENAME(Validator): Does *not* ensure the file type in any way. Returns validation failure if no data was uploaded. - Arguments:: - - filename: filename (before dot) regex - extension: extension (after dot) regex - lastdot: which dot should be used as a filename / extension separator: - True means last dot, eg. file.png -> file / png - False means first dot, eg. file.tar.gz -> file / tar.gz - case: 0 - keep the case, 1 - transform the string into lowercase (default), - 2 - transform the string into uppercase + Args: + filename: filename (before dot) regex + extension: extension (after dot) regex + lastdot: which dot should be used as a filename / extension separator: + True means last dot, eg. file.png -> file / png + False means first dot, eg. file.tar.gz -> file / tar.gz + case: 0 - keep the case, 1 - transform the string into lowercase (default), + 2 - transform the string into uppercase If there is no dot present, extension checks will be done against empty string and filename checks against whole value. - Examples:: + Examples: + Check if file has a pdf extension (case insensitive): - #Check if file has a pdf extension (case insensitive): INPUT(_type='file', _name='name', - requires=IS_UPLOAD_FILENAME(extension='pdf')) + requires=IS_UPLOAD_FILENAME(extension='pdf')) + + Check if file has a tar.gz extension and name starting with backup: - #Check if file has a tar.gz extension and name starting with backup: INPUT(_type='file', _name='name', - requires=IS_UPLOAD_FILENAME(filename='backup.*', + requires=IS_UPLOAD_FILENAME(filename='backup.*', extension='tar.gz', lastdot=False)) - #Check if file has no extension and name matching README - #(case sensitive): - INPUT(_type='file', _name='name', - requires=IS_UPLOAD_FILENAME(filename='^README$', - extension='^$', case=0)) + Check if file has no extension and name matching README + (case sensitive): + + INPUT(_type='file', _name='name', + requires=IS_UPLOAD_FILENAME(filename='^README$', + extension='^$', case=0) + """ def __init__(self, filename=None, extension=None, lastdot=True, case=1, @@ -3273,28 +3327,28 @@ class IS_IPV4(Validator): IPv4 regex taken from: http://regexlib.com/REDetails.aspx?regexp_id=1411 - Arguments: + Args: - minip: lowest allowed address; accepts: - str, eg. 192.168.0.1 - list or tuple of octets, eg. [192, 168, 0, 1] - maxip: highest allowed address; same as above - invert: True to allow addresses only from outside of given range; note + minip: lowest allowed address; accepts: + + - str, eg. 192.168.0.1 + - list or tuple of octets, eg. [192, 168, 0, 1] + maxip: highest allowed address; same as above + invert: True to allow addresses only from outside of given range; note that range boundaries are not matched this way - is_localhost: localhost address treatment: - None (default): indifferent - True (enforce): query address must match localhost address - (127.0.0.1) - False (forbid): query address must not match localhost - address - is_private: same as above, except that query address is checked against - two address ranges: 172.16.0.0 - 172.31.255.255 and - 192.168.0.0 - 192.168.255.255 - is_automatic: same as above, except that query address is checked against - one address range: 169.254.0.0 - 169.254.255.255 + is_localhost: localhost address treatment: + + - None (default): indifferent + - True (enforce): query address must match localhost address (127.0.0.1) + - False (forbid): query address must not match localhost address + is_private: same as above, except that query address is checked against + two address ranges: 172.16.0.0 - 172.31.255.255 and + 192.168.0.0 - 192.168.255.255 + is_automatic: same as above, except that query address is checked against + one address range: 169.254.0.0 - 169.254.255.255 Minip and maxip may also be lists or tuples of addresses in all above - forms (str, int, list / tuple), allowing setup of multiple address ranges: + forms (str, int, list / tuple), allowing setup of multiple address ranges:: minip = (minip1, minip2, ... minipN) | | | @@ -3303,61 +3357,66 @@ class IS_IPV4(Validator): Longer iterable will be truncated to match length of shorter one. - Examples:: + Examples: + Check for valid IPv4 address: - #Check for valid IPv4 address: - INPUT(_type='text', _name='name', requires=IS_IPV4()) + INPUT(_type='text', _name='name', requires=IS_IPV4()) - #Check for valid IPv4 address belonging to specific range: - INPUT(_type='text', _name='name', - requires=IS_IPV4(minip='100.200.0.0', maxip='100.200.255.255')) + Check for valid IPv4 address belonging to specific range: - #Check for valid IPv4 address belonging to either 100.110.0.0 - - #100.110.255.255 or 200.50.0.0 - 200.50.0.255 address range: - INPUT(_type='text', _name='name', - requires=IS_IPV4(minip=('100.110.0.0', '200.50.0.0'), + INPUT(_type='text', _name='name', + requires=IS_IPV4(minip='100.200.0.0', maxip='100.200.255.255')) + + Check for valid IPv4 address belonging to either 100.110.0.0 - + 100.110.255.255 or 200.50.0.0 - 200.50.0.255 address range: + + INPUT(_type='text', _name='name', + requires=IS_IPV4(minip=('100.110.0.0', '200.50.0.0'), maxip=('100.110.255.255', '200.50.0.255'))) - #Check for valid IPv4 address belonging to private address space: - INPUT(_type='text', _name='name', requires=IS_IPV4(is_private=True)) + Check for valid IPv4 address belonging to private address space: - #Check for valid IPv4 address that is not a localhost address: - INPUT(_type='text', _name='name', requires=IS_IPV4(is_localhost=False)) + INPUT(_type='text', _name='name', requires=IS_IPV4(is_private=True)) + + Check for valid IPv4 address that is not a localhost address: + + INPUT(_type='text', _name='name', requires=IS_IPV4(is_localhost=False)) + + >>> IS_IPV4()('1.2.3.4') + ('1.2.3.4', None) + >>> IS_IPV4()('255.255.255.255') + ('255.255.255.255', None) + >>> IS_IPV4()('1.2.3.4 ') + ('1.2.3.4 ', 'enter valid IPv4 address') + >>> IS_IPV4()('1.2.3.4.5') + ('1.2.3.4.5', 'enter valid IPv4 address') + >>> IS_IPV4()('123.123') + ('123.123', 'enter valid IPv4 address') + >>> IS_IPV4()('1111.2.3.4') + ('1111.2.3.4', 'enter valid IPv4 address') + >>> IS_IPV4()('0111.2.3.4') + ('0111.2.3.4', 'enter valid IPv4 address') + >>> IS_IPV4()('256.2.3.4') + ('256.2.3.4', 'enter valid IPv4 address') + >>> IS_IPV4()('300.2.3.4') + ('300.2.3.4', 'enter valid IPv4 address') + >>> IS_IPV4(minip='1.2.3.4', maxip='1.2.3.4')('1.2.3.4') + ('1.2.3.4', None) + >>> IS_IPV4(minip='1.2.3.5', maxip='1.2.3.9', error_message='Bad ip')('1.2.3.4') + ('1.2.3.4', 'bad ip') + >>> IS_IPV4(maxip='1.2.3.4', invert=True)('127.0.0.1') + ('127.0.0.1', None) + >>> IS_IPV4(maxip='1.2.3.4', invert=True)('1.2.3.4') + ('1.2.3.4', 'enter valid IPv4 address') + >>> IS_IPV4(is_localhost=True)('127.0.0.1') + ('127.0.0.1', None) + >>> IS_IPV4(is_localhost=True)('1.2.3.4') + ('1.2.3.4', 'enter valid IPv4 address') + >>> IS_IPV4(is_localhost=False)('127.0.0.1') + ('127.0.0.1', 'enter valid IPv4 address') + >>> IS_IPV4(maxip='100.0.0.0', is_localhost=True)('127.0.0.1') + ('127.0.0.1', 'enter valid IPv4 address') - >>> IS_IPV4()('1.2.3.4') - ('1.2.3.4', None) - >>> IS_IPV4()('255.255.255.255') - ('255.255.255.255', None) - >>> IS_IPV4()('1.2.3.4 ') - ('1.2.3.4 ', 'enter valid IPv4 address') - >>> IS_IPV4()('1.2.3.4.5') - ('1.2.3.4.5', 'enter valid IPv4 address') - >>> IS_IPV4()('123.123') - ('123.123', 'enter valid IPv4 address') - >>> IS_IPV4()('1111.2.3.4') - ('1111.2.3.4', 'enter valid IPv4 address') - >>> IS_IPV4()('0111.2.3.4') - ('0111.2.3.4', 'enter valid IPv4 address') - >>> IS_IPV4()('256.2.3.4') - ('256.2.3.4', 'enter valid IPv4 address') - >>> IS_IPV4()('300.2.3.4') - ('300.2.3.4', 'enter valid IPv4 address') - >>> IS_IPV4(minip='1.2.3.4', maxip='1.2.3.4')('1.2.3.4') - ('1.2.3.4', None) - >>> IS_IPV4(minip='1.2.3.5', maxip='1.2.3.9', error_message='Bad ip')('1.2.3.4') - ('1.2.3.4', 'bad ip') - >>> IS_IPV4(maxip='1.2.3.4', invert=True)('127.0.0.1') - ('127.0.0.1', None) - >>> IS_IPV4(maxip='1.2.3.4', invert=True)('1.2.3.4') - ('1.2.3.4', 'enter valid IPv4 address') - >>> IS_IPV4(is_localhost=True)('127.0.0.1') - ('127.0.0.1', None) - >>> IS_IPV4(is_localhost=True)('1.2.3.4') - ('1.2.3.4', 'enter valid IPv4 address') - >>> IS_IPV4(is_localhost=False)('127.0.0.1') - ('127.0.0.1', 'enter valid IPv4 address') - >>> IS_IPV4(maxip='100.0.0.0', is_localhost=True)('127.0.0.1') - ('127.0.0.1', 'enter valid IPv4 address') """ regex = re.compile( @@ -3433,61 +3492,64 @@ class IS_IPV6(Validator): use the ipaddress library and falls back to contrib/ipaddr.py from Google (https://code.google.com/p/ipaddr-py/) - Arguments: - is_private: None (default): indifferent - True (enforce): address must be in fc00::/7 range - False (forbid): address must NOT be in fc00::/7 range - is_link_local: Same as above but uses fe80::/10 range - is_reserved: Same as above but uses IETF reserved range - is_mulicast: Same as above but uses ff00::/8 range - is_routeable: Similar to above but enforces not private, link_local, - reserved or multicast - is_6to4: Same as above but uses 2002::/16 range - is_teredo: Same as above but uses 2001::/32 range - subnets: value must be a member of at least one from list of subnets + Args: + is_private: None (default): indifferent + True (enforce): address must be in fc00::/7 range + False (forbid): address must NOT be in fc00::/7 range + is_link_local: Same as above but uses fe80::/10 range + is_reserved: Same as above but uses IETF reserved range + is_mulicast: Same as above but uses ff00::/8 range + is_routeable: Similar to above but enforces not private, link_local, + reserved or multicast + is_6to4: Same as above but uses 2002::/16 range + is_teredo: Same as above but uses 2001::/32 range + subnets: value must be a member of at least one from list of subnets Examples: + Check for valid IPv6 address: - #Check for valid IPv6 address: - INPUT(_type='text', _name='name', requires=IS_IPV6()) + INPUT(_type='text', _name='name', requires=IS_IPV6()) - #Check for valid IPv6 address is a link_local address: - INPUT(_type='text', _name='name', requires=IS_IPV6(is_link_local=True)) + Check for valid IPv6 address is a link_local address: - #Check for valid IPv6 address that is Internet routeable: - INPUT(_type='text', _name='name', requires=IS_IPV6(is_routeable=True)) + INPUT(_type='text', _name='name', requires=IS_IPV6(is_link_local=True)) - #Check for valid IPv6 address in specified subnet: - INPUT(_type='text', _name='name', requires=IS_IPV6(subnets=['2001::/32']) + Check for valid IPv6 address that is Internet routeable: - >>> IS_IPV6()('fe80::126c:8ffa:fe22:b3af') - ('fe80::126c:8ffa:fe22:b3af', None) - >>> IS_IPV6()('192.168.1.1') - ('192.168.1.1', 'enter valid IPv6 address') - >>> IS_IPV6(error_message='Bad ip')('192.168.1.1') - ('192.168.1.1', 'bad ip') - >>> IS_IPV6(is_link_local=True)('fe80::126c:8ffa:fe22:b3af') - ('fe80::126c:8ffa:fe22:b3af', None) - >>> IS_IPV6(is_link_local=False)('fe80::126c:8ffa:fe22:b3af') - ('fe80::126c:8ffa:fe22:b3af', 'enter valid IPv6 address') - >>> IS_IPV6(is_link_local=True)('2001::126c:8ffa:fe22:b3af') - ('2001::126c:8ffa:fe22:b3af', 'enter valid IPv6 address') - >>> IS_IPV6(is_multicast=True)('2001::126c:8ffa:fe22:b3af') - ('2001::126c:8ffa:fe22:b3af', 'enter valid IPv6 address') - >>> IS_IPV6(is_multicast=True)('ff00::126c:8ffa:fe22:b3af') - ('ff00::126c:8ffa:fe22:b3af', None) - >>> IS_IPV6(is_routeable=True)('2001::126c:8ffa:fe22:b3af') - ('2001::126c:8ffa:fe22:b3af', None) - >>> IS_IPV6(is_routeable=True)('ff00::126c:8ffa:fe22:b3af') - ('ff00::126c:8ffa:fe22:b3af', 'enter valid IPv6 address') - >>> IS_IPV6(subnets='2001::/32')('2001::8ffa:fe22:b3af') - ('2001::8ffa:fe22:b3af', None) - >>> IS_IPV6(subnets='fb00::/8')('2001::8ffa:fe22:b3af') - ('2001::8ffa:fe22:b3af', 'enter valid IPv6 address') - >>> IS_IPV6(subnets=['fc00::/8','2001::/32'])('2001::8ffa:fe22:b3af') - ('2001::8ffa:fe22:b3af', None) - >>> IS_IPV6(subnets='invalidsubnet')('2001::8ffa:fe22:b3af') - ('2001::8ffa:fe22:b3af', 'invalid subnet provided') + INPUT(_type='text', _name='name', requires=IS_IPV6(is_routeable=True)) + + Check for valid IPv6 address in specified subnet: + + INPUT(_type='text', _name='name', requires=IS_IPV6(subnets=['2001::/32']) + + >>> IS_IPV6()('fe80::126c:8ffa:fe22:b3af') + ('fe80::126c:8ffa:fe22:b3af', None) + >>> IS_IPV6()('192.168.1.1') + ('192.168.1.1', 'enter valid IPv6 address') + >>> IS_IPV6(error_message='Bad ip')('192.168.1.1') + ('192.168.1.1', 'bad ip') + >>> IS_IPV6(is_link_local=True)('fe80::126c:8ffa:fe22:b3af') + ('fe80::126c:8ffa:fe22:b3af', None) + >>> IS_IPV6(is_link_local=False)('fe80::126c:8ffa:fe22:b3af') + ('fe80::126c:8ffa:fe22:b3af', 'enter valid IPv6 address') + >>> IS_IPV6(is_link_local=True)('2001::126c:8ffa:fe22:b3af') + ('2001::126c:8ffa:fe22:b3af', 'enter valid IPv6 address') + >>> IS_IPV6(is_multicast=True)('2001::126c:8ffa:fe22:b3af') + ('2001::126c:8ffa:fe22:b3af', 'enter valid IPv6 address') + >>> IS_IPV6(is_multicast=True)('ff00::126c:8ffa:fe22:b3af') + ('ff00::126c:8ffa:fe22:b3af', None) + >>> IS_IPV6(is_routeable=True)('2001::126c:8ffa:fe22:b3af') + ('2001::126c:8ffa:fe22:b3af', None) + >>> IS_IPV6(is_routeable=True)('ff00::126c:8ffa:fe22:b3af') + ('ff00::126c:8ffa:fe22:b3af', 'enter valid IPv6 address') + >>> IS_IPV6(subnets='2001::/32')('2001::8ffa:fe22:b3af') + ('2001::8ffa:fe22:b3af', None) + >>> IS_IPV6(subnets='fb00::/8')('2001::8ffa:fe22:b3af') + ('2001::8ffa:fe22:b3af', 'enter valid IPv6 address') + >>> IS_IPV6(subnets=['fc00::/8','2001::/32'])('2001::8ffa:fe22:b3af') + ('2001::8ffa:fe22:b3af', None) + >>> IS_IPV6(subnets='invalidsubnet')('2001::8ffa:fe22:b3af') + ('2001::8ffa:fe22:b3af', 'invalid subnet provided') """ @@ -3577,48 +3639,51 @@ class IS_IPADDRESS(Validator): Uses ipaddress library if found, falls back to PEP-3144 ipaddr.py from Google (in contrib). - Universal arguments: - - minip: lowest allowed address; accepts: - str, eg. 192.168.0.1 - list or tuple of octets, eg. [192, 168, 0, 1] - maxip: highest allowed address; same as above - invert: True to allow addresses only from outside of given range; note - that range boundaries are not matched this way + Args: + minip: lowest allowed address; accepts: + str, eg. 192.168.0.1 + list or tuple of octets, eg. [192, 168, 0, 1] + maxip: highest allowed address; same as above + invert: True to allow addresses only from outside of given range; note + that range boundaries are not matched this way IPv4 specific arguments: - is_localhost: localhost address treatment: - None (default): indifferent - True (enforce): query address must match localhost address - (127.0.0.1) - False (forbid): query address must not match localhost - address - is_private: same as above, except that query address is checked against - two address ranges: 172.16.0.0 - 172.31.255.255 and - 192.168.0.0 - 192.168.255.255 - is_automatic: same as above, except that query address is checked against - one address range: 169.254.0.0 - 169.254.255.255 - is_ipv4: None (default): indifferent - True (enforce): must be an IPv4 address - False (forbid): must NOT be an IPv4 address + - is_localhost: localhost address treatment: + + - None (default): indifferent + - True (enforce): query address must match localhost address + (127.0.0.1) + - False (forbid): query address must not match localhost address + - is_private: same as above, except that query address is checked against + two address ranges: 172.16.0.0 - 172.31.255.255 and + 192.168.0.0 - 192.168.255.255 + - is_automatic: same as above, except that query address is checked against + one address range: 169.254.0.0 - 169.254.255.255 + - is_ipv4: either: + + - None (default): indifferent + - True (enforce): must be an IPv4 address + - False (forbid): must NOT be an IPv4 address IPv6 specific arguments: - is_link_local: Same as above but uses fe80::/10 range - is_reserved: Same as above but uses IETF reserved range - is_mulicast: Same as above but uses ff00::/8 range - is_routeable: Similar to above but enforces not private, link_local, - reserved or multicast - is_6to4: Same as above but uses 2002::/16 range - is_teredo: Same as above but uses 2001::/32 range - subnets: value must be a member of at least one from list of subnets - is_ipv6: None (default): indifferent - True (enforce): must be an IPv6 address - False (forbid): must NOT be an IPv6 address + - is_link_local: Same as above but uses fe80::/10 range + - is_reserved: Same as above but uses IETF reserved range + - is_mulicast: Same as above but uses ff00::/8 range + - is_routeable: Similar to above but enforces not private, link_local, + reserved or multicast + - is_6to4: Same as above but uses 2002::/16 range + - is_teredo: Same as above but uses 2001::/32 range + - subnets: value must be a member of at least one from list of subnets + - is_ipv6: either: + + - None (default): indifferent + - True (enforce): must be an IPv6 address + - False (forbid): must NOT be an IPv6 address Minip and maxip may also be lists or tuples of addresses in all above - forms (str, int, list / tuple), allowing setup of multiple address ranges: + forms (str, int, list / tuple), allowing setup of multiple address ranges:: minip = (minip1, minip2, ... minipN) | | | @@ -3627,77 +3692,77 @@ class IS_IPADDRESS(Validator): Longer iterable will be truncated to match length of shorter one. - >>> IS_IPADDRESS()('192.168.1.5') - ('192.168.1.5', None) - >>> IS_IPADDRESS(is_ipv6=False)('192.168.1.5') - ('192.168.1.5', None) - >>> IS_IPADDRESS()('255.255.255.255') - ('255.255.255.255', None) - >>> IS_IPADDRESS()('192.168.1.5 ') - ('192.168.1.5 ', 'enter valid IP address') - >>> IS_IPADDRESS()('192.168.1.1.5') - ('192.168.1.1.5', 'enter valid IP address') - >>> IS_IPADDRESS()('123.123') - ('123.123', 'enter valid IP address') - >>> IS_IPADDRESS()('1111.2.3.4') - ('1111.2.3.4', 'enter valid IP address') - >>> IS_IPADDRESS()('0111.2.3.4') - ('0111.2.3.4', 'enter valid IP address') - >>> IS_IPADDRESS()('256.2.3.4') - ('256.2.3.4', 'enter valid IP address') - >>> IS_IPADDRESS()('300.2.3.4') - ('300.2.3.4', 'enter valid IP address') - >>> IS_IPADDRESS(minip='192.168.1.0', maxip='192.168.1.255')('192.168.1.100') - ('192.168.1.100', None) - >>> IS_IPADDRESS(minip='1.2.3.5', maxip='1.2.3.9', error_message='Bad ip')('1.2.3.4') - ('1.2.3.4', 'bad ip') - >>> IS_IPADDRESS(maxip='1.2.3.4', invert=True)('127.0.0.1') - ('127.0.0.1', None) - >>> IS_IPADDRESS(maxip='192.168.1.4', invert=True)('192.168.1.4') - ('192.168.1.4', 'enter valid IP address') - >>> IS_IPADDRESS(is_localhost=True)('127.0.0.1') - ('127.0.0.1', None) - >>> IS_IPADDRESS(is_localhost=True)('192.168.1.10') - ('192.168.1.10', 'enter valid IP address') - >>> IS_IPADDRESS(is_localhost=False)('127.0.0.1') - ('127.0.0.1', 'enter valid IP address') - >>> IS_IPADDRESS(maxip='100.0.0.0', is_localhost=True)('127.0.0.1') - ('127.0.0.1', 'enter valid IP address') + >>> IS_IPADDRESS()('192.168.1.5') + ('192.168.1.5', None) + >>> IS_IPADDRESS(is_ipv6=False)('192.168.1.5') + ('192.168.1.5', None) + >>> IS_IPADDRESS()('255.255.255.255') + ('255.255.255.255', None) + >>> IS_IPADDRESS()('192.168.1.5 ') + ('192.168.1.5 ', 'enter valid IP address') + >>> IS_IPADDRESS()('192.168.1.1.5') + ('192.168.1.1.5', 'enter valid IP address') + >>> IS_IPADDRESS()('123.123') + ('123.123', 'enter valid IP address') + >>> IS_IPADDRESS()('1111.2.3.4') + ('1111.2.3.4', 'enter valid IP address') + >>> IS_IPADDRESS()('0111.2.3.4') + ('0111.2.3.4', 'enter valid IP address') + >>> IS_IPADDRESS()('256.2.3.4') + ('256.2.3.4', 'enter valid IP address') + >>> IS_IPADDRESS()('300.2.3.4') + ('300.2.3.4', 'enter valid IP address') + >>> IS_IPADDRESS(minip='192.168.1.0', maxip='192.168.1.255')('192.168.1.100') + ('192.168.1.100', None) + >>> IS_IPADDRESS(minip='1.2.3.5', maxip='1.2.3.9', error_message='Bad ip')('1.2.3.4') + ('1.2.3.4', 'bad ip') + >>> IS_IPADDRESS(maxip='1.2.3.4', invert=True)('127.0.0.1') + ('127.0.0.1', None) + >>> IS_IPADDRESS(maxip='192.168.1.4', invert=True)('192.168.1.4') + ('192.168.1.4', 'enter valid IP address') + >>> IS_IPADDRESS(is_localhost=True)('127.0.0.1') + ('127.0.0.1', None) + >>> IS_IPADDRESS(is_localhost=True)('192.168.1.10') + ('192.168.1.10', 'enter valid IP address') + >>> IS_IPADDRESS(is_localhost=False)('127.0.0.1') + ('127.0.0.1', 'enter valid IP address') + >>> IS_IPADDRESS(maxip='100.0.0.0', is_localhost=True)('127.0.0.1') + ('127.0.0.1', 'enter valid IP address') - >>> IS_IPADDRESS()('fe80::126c:8ffa:fe22:b3af') - ('fe80::126c:8ffa:fe22:b3af', None) - >>> IS_IPADDRESS(is_ipv4=False)('fe80::126c:8ffa:fe22:b3af') - ('fe80::126c:8ffa:fe22:b3af', None) - >>> IS_IPADDRESS()('fe80::126c:8ffa:fe22:b3af ') - ('fe80::126c:8ffa:fe22:b3af ', 'enter valid IP address') - >>> IS_IPADDRESS(is_ipv4=True)('fe80::126c:8ffa:fe22:b3af') - ('fe80::126c:8ffa:fe22:b3af', 'enter valid IP address') - >>> IS_IPADDRESS(is_ipv6=True)('192.168.1.1') - ('192.168.1.1', 'enter valid IP address') - >>> IS_IPADDRESS(is_ipv6=True, error_message='Bad ip')('192.168.1.1') - ('192.168.1.1', 'bad ip') - >>> IS_IPADDRESS(is_link_local=True)('fe80::126c:8ffa:fe22:b3af') - ('fe80::126c:8ffa:fe22:b3af', None) - >>> IS_IPADDRESS(is_link_local=False)('fe80::126c:8ffa:fe22:b3af') - ('fe80::126c:8ffa:fe22:b3af', 'enter valid IP address') - >>> IS_IPADDRESS(is_link_local=True)('2001::126c:8ffa:fe22:b3af') - ('2001::126c:8ffa:fe22:b3af', 'enter valid IP address') - >>> IS_IPADDRESS(is_multicast=True)('2001::126c:8ffa:fe22:b3af') - ('2001::126c:8ffa:fe22:b3af', 'enter valid IP address') - >>> IS_IPADDRESS(is_multicast=True)('ff00::126c:8ffa:fe22:b3af') - ('ff00::126c:8ffa:fe22:b3af', None) - >>> IS_IPADDRESS(is_routeable=True)('2001::126c:8ffa:fe22:b3af') - ('2001::126c:8ffa:fe22:b3af', None) - >>> IS_IPADDRESS(is_routeable=True)('ff00::126c:8ffa:fe22:b3af') - ('ff00::126c:8ffa:fe22:b3af', 'enter valid IP address') - >>> IS_IPADDRESS(subnets='2001::/32')('2001::8ffa:fe22:b3af') - ('2001::8ffa:fe22:b3af', None) - >>> IS_IPADDRESS(subnets='fb00::/8')('2001::8ffa:fe22:b3af') - ('2001::8ffa:fe22:b3af', 'enter valid IP address') - >>> IS_IPADDRESS(subnets=['fc00::/8','2001::/32'])('2001::8ffa:fe22:b3af') - ('2001::8ffa:fe22:b3af', None) - >>> IS_IPADDRESS(subnets='invalidsubnet')('2001::8ffa:fe22:b3af') - ('2001::8ffa:fe22:b3af', 'invalid subnet provided') + >>> IS_IPADDRESS()('fe80::126c:8ffa:fe22:b3af') + ('fe80::126c:8ffa:fe22:b3af', None) + >>> IS_IPADDRESS(is_ipv4=False)('fe80::126c:8ffa:fe22:b3af') + ('fe80::126c:8ffa:fe22:b3af', None) + >>> IS_IPADDRESS()('fe80::126c:8ffa:fe22:b3af ') + ('fe80::126c:8ffa:fe22:b3af ', 'enter valid IP address') + >>> IS_IPADDRESS(is_ipv4=True)('fe80::126c:8ffa:fe22:b3af') + ('fe80::126c:8ffa:fe22:b3af', 'enter valid IP address') + >>> IS_IPADDRESS(is_ipv6=True)('192.168.1.1') + ('192.168.1.1', 'enter valid IP address') + >>> IS_IPADDRESS(is_ipv6=True, error_message='Bad ip')('192.168.1.1') + ('192.168.1.1', 'bad ip') + >>> IS_IPADDRESS(is_link_local=True)('fe80::126c:8ffa:fe22:b3af') + ('fe80::126c:8ffa:fe22:b3af', None) + >>> IS_IPADDRESS(is_link_local=False)('fe80::126c:8ffa:fe22:b3af') + ('fe80::126c:8ffa:fe22:b3af', 'enter valid IP address') + >>> IS_IPADDRESS(is_link_local=True)('2001::126c:8ffa:fe22:b3af') + ('2001::126c:8ffa:fe22:b3af', 'enter valid IP address') + >>> IS_IPADDRESS(is_multicast=True)('2001::126c:8ffa:fe22:b3af') + ('2001::126c:8ffa:fe22:b3af', 'enter valid IP address') + >>> IS_IPADDRESS(is_multicast=True)('ff00::126c:8ffa:fe22:b3af') + ('ff00::126c:8ffa:fe22:b3af', None) + >>> IS_IPADDRESS(is_routeable=True)('2001::126c:8ffa:fe22:b3af') + ('2001::126c:8ffa:fe22:b3af', None) + >>> IS_IPADDRESS(is_routeable=True)('ff00::126c:8ffa:fe22:b3af') + ('ff00::126c:8ffa:fe22:b3af', 'enter valid IP address') + >>> IS_IPADDRESS(subnets='2001::/32')('2001::8ffa:fe22:b3af') + ('2001::8ffa:fe22:b3af', None) + >>> IS_IPADDRESS(subnets='fb00::/8')('2001::8ffa:fe22:b3af') + ('2001::8ffa:fe22:b3af', 'enter valid IP address') + >>> IS_IPADDRESS(subnets=['fc00::/8','2001::/32'])('2001::8ffa:fe22:b3af') + ('2001::8ffa:fe22:b3af', None) + >>> IS_IPADDRESS(subnets='invalidsubnet')('2001::8ffa:fe22:b3af') + ('2001::8ffa:fe22:b3af', 'invalid subnet provided') """ def __init__( self, diff --git a/gluon/widget.py b/gluon/widget.py index 7f29960b..2214145e 100644 --- a/gluon/widget.py +++ b/gluon/widget.py @@ -2,11 +2,12 @@ # -*- coding: utf-8 -*- """ -This file is part of the web2py Web Framework -Copyrighted by Massimo Di Pierro -License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) +| This file is part of the web2py Web Framework +| Copyrighted by Massimo Di Pierro +| 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) @@ -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') @@ -1054,7 +1055,7 @@ def start_schedulers(options): def start(cron=True): - """ Start server """ + """ Starts server """ # ## get command line arguments diff --git a/gluon/xmlrpc.py b/gluon/xmlrpc.py index 7fa29978..4957e995 100644 --- a/gluon/xmlrpc.py +++ b/gluon/xmlrpc.py @@ -2,9 +2,9 @@ # -*- coding: utf-8 -*- """ -This file is part of the web2py Web Framework -Copyrighted by Massimo Di Pierro -License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) +| This file is part of the web2py Web Framework +| Copyrighted by Massimo Di Pierro +| License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) """ from SimpleXMLRPCServer import SimpleXMLRPCDispatcher