diff --git a/gluon/sanitizer.py b/gluon/sanitizer.py index b91199f3..a85b256a 100644 --- a/gluon/sanitizer.py +++ b/gluon/sanitizer.py @@ -1,15 +1,13 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- - """ -:: - - # from http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/496942 - # Title: Cross-site scripting (XSS) defense - # Submitter: Josh Goldfoot (other recipes) - # Last Updated: 2006/08/05 - # Version no: 1.0 +| From http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/496942 +| Submitter: Josh Goldfoot (other recipes) +| Last Updated: 2006/08/05 +| Version: 1.0 +Cross-site scripting (XSS) defense +----------------------------------- """ diff --git a/gluon/scheduler.py b/gluon/scheduler.py index 4aae4539..37f50012 100644 --- a/gluon/scheduler.py +++ b/gluon/scheduler.py @@ -1,5 +1,13 @@ #!/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) + +Background processes made simple +--------------------------------- +""" USAGE = """ ## Example @@ -118,6 +126,9 @@ CALLABLETYPES = (types.LambdaType, types.FunctionType, class Task(object): + """Defines a "task" object that gets passed from the main thread to the + executor's one + """ def __init__(self, app, function, timeout, args='[]', vars='{}', **kwargs): logger.debug(' new task allocated: %s.%s', app, function) self.app = app @@ -132,6 +143,9 @@ class Task(object): class TaskReport(object): + """Defines a "task report" object that gets passed from the executor's + thread to the main one + """ def __init__(self, status, result=None, output=None, tb=None): logger.debug(' new task report: %s', status) if tb: @@ -184,7 +198,7 @@ def _decode_dict(dct): def executor(queue, task, out): - """ the background process """ + """The function used to execute tasks in the background process""" logger.debug(' task started') class LogOutput(object): @@ -249,20 +263,28 @@ def executor(queue, task, out): class MetaScheduler(threading.Thread): + """Base class documenting scheduler's base methods""" + def __init__(self): threading.Thread.__init__(self) self.process = None # the background process self.have_heartbeat = True # set to False to kill self.empty_runs = 0 - def async(self, task): - """ - starts the background process and returns: - ('ok',result,output) - ('error',exception,None) - ('timeout',None,None) - ('terminated',None,None) + """Starts the background process + + Args: + task : a `Task` object + + Returns: + tuple: containing:: + + ('ok',result,output) + ('error',exception,None) + ('timeout',None,None) + ('terminated',None,None) + """ db = self.db sr = db.scheduler_run @@ -332,22 +354,27 @@ class MetaScheduler(threading.Thread): return tr def die(self): + """Forces termination of the worker process along with any running + task""" logger.info('die!') self.have_heartbeat = False self.terminate_process() def give_up(self): + """Waits for any running task to be executed, then exits the worker + process""" logger.info('Giving up as soon as possible!') self.have_heartbeat = False def terminate_process(self): + """Terminates any running tasks (internal use only)""" try: self.process.terminate() except: pass # no process to terminate def run(self): - """ the thread that sends heartbeat """ + """This is executed by the main thread to send heartbeats""" counter = 0 while self.have_heartbeat: self.send_heartbeat(counter) @@ -361,6 +388,7 @@ class MetaScheduler(threading.Thread): time.sleep(1) def pop_task(self): + """Fetches a task ready to be executed""" return Task( app=None, function='demo_function', @@ -369,6 +397,7 @@ class MetaScheduler(threading.Thread): vars='{}') def report_task(self, task, task_report): + """Creates a task report""" print 'reporting task' pass @@ -376,6 +405,8 @@ class MetaScheduler(threading.Thread): pass def loop(self): + """Main loop, fetching tasks and starting executor's background + processes""" try: self.start_heartbeats() while True and self.have_heartbeat: @@ -406,7 +437,8 @@ WORKER_STATUS = (ACTIVE, PICK, DISABLED, TERMINATE, KILL, STOP_TASK) class TYPE(object): """ - validator that check whether field is valid json and validate its type + Validator that checks whether field is valid json and validates its type. + Used for `args` and `vars` of the scheduler_task table """ def __init__(self, myclass=list, parse=False): @@ -430,6 +462,32 @@ class TYPE(object): class Scheduler(MetaScheduler): + """Scheduler object + + Args: + db: DAL connection where Scheduler will create its tables + tasks(dict): either a dict containing name-->func or None. + If None, functions will be searched in the environment + migrate(bool): turn migration on/off for the Scheduler's tables + worker_name(str): force worker_name to identify each process. + Leave it to None to autoassign a name (hostname#pid) + group_names(list): process tasks belonging to this group + heartbeat(int): how many seconds the worker sleeps between one execution + and the following one. Indirectly sets how many seconds will pass + between checks for new tasks + max_empty_runs(int): how many loops are allowed to pass without + processing any tasks before exiting the process. 0 to keep always + the process alive + discard_results(bool): Scheduler stores executions's details into the + scheduler_run table. By default, only if there is a result the + details are kept. Turning this to True means discarding results + even for tasks that return something + utc_time(bool): do all datetime calculations assuming UTC as the + timezone. Remember to pass `start_time` and `stop_time` to tasks + accordingly + + """ + def __init__(self, db, tasks=None, migrate=True, worker_name=None, group_names=['main'], heartbeat=HEARTBEAT, max_empty_runs=0, discard_results=False, utc_time=False): @@ -467,9 +525,11 @@ class Scheduler(MetaScheduler): return True def now(self): + """Shortcut that fetches current time based on UTC preferences""" return self.utc_time and datetime.datetime.utcnow() or datetime.datetime.now() def set_requirements(self, scheduler_task): + """Called to set defaults for lazy_tables connections""" from gluon import current if hasattr(current, 'request'): scheduler_task.application_name.default = '%s/%s' % ( @@ -477,6 +537,7 @@ class Scheduler(MetaScheduler): ) def define_tables(self, db, migrate): + """Defines Scheduler tables structure""" from gluon.dal import DEFAULT logger.debug('defining tables (migrate=%s)', migrate) now = self.now @@ -550,6 +611,23 @@ class Scheduler(MetaScheduler): db.commit() def loop(self, worker_name=None): + """Main loop + + This works basically as a neverending loop that: + + - checks if the worker is ready to process tasks (is not DISABLED) + - pops a task from the queue + - if there is a task: + + - spawns the executor background process + - waits for the process to be finished + - sleeps `heartbeat` seconds + - if there is not a task: + + - checks for max_empty_runs + - sleeps `heartbeat` seconds + + """ signal.signal(signal.SIGTERM, lambda signum, stack_frame: sys.exit(1)) try: self.start_heartbeats() @@ -581,6 +659,10 @@ class Scheduler(MetaScheduler): self.die() def wrapped_assign_tasks(self, db): + """Commodity function to call `assign_tasks` and trap exceptions + If an exception is raised, assume it happened because of database + contention and retries `assign_task` after 0.5 seconds + """ logger.debug('Assigning tasks...') db.commit() #db.commit() only for Mysql x = 0 @@ -597,6 +679,10 @@ class Scheduler(MetaScheduler): time.sleep(0.5) def wrapped_pop_task(self): + """Commodity function to call `pop_task` and trap exceptions + If an exception is raised, assume it happened because of database + contention and retries `pop_task` after 0.5 seconds + """ db = self.db db.commit() #another nifty db.commit() only for Mysql x = 0 @@ -612,6 +698,7 @@ class Scheduler(MetaScheduler): time.sleep(0.5) def pop_task(self, db): + """Grabs a task ready to be executed from the queue""" now = self.now() st = self.db.scheduler_task if self.is_a_ticker and self.do_assign_tasks: @@ -685,6 +772,8 @@ class Scheduler(MetaScheduler): uuid=task.uuid) def report_task(self, task, task_report): + """Takes care of storing the result according to preferences + and deals with logic for repeating tasks""" db = self.db now = self.now() while True: @@ -744,12 +833,25 @@ class Scheduler(MetaScheduler): time.sleep(0.5) def adj_hibernation(self): + """Used to increase the "sleep" interval for DISABLED workers""" if self.worker_status[0] == DISABLED: wk_st = self.worker_status[1] hibernation = wk_st + 1 if wk_st < MAXHIBERNATION else MAXHIBERNATION self.worker_status[1] = hibernation def send_heartbeat(self, counter): + """This function is vital for proper coordination among available + workers. + It: + + - sends the heartbeat + - elects a ticker among available workers (the only process that + effectively dispatch tasks to workers) + - deals with worker's statuses + - does "housecleaning" for dead workers + - triggers tasks assignment to workers + + """ if not self.db_thread: logger.debug('thread building own DAL object') self.db_thread = DAL( @@ -828,6 +930,10 @@ class Scheduler(MetaScheduler): self.sleep() def being_a_ticker(self): + """Elects a TICKER process that assigns tasks to available workers. + Does its best to elect a worker that is not busy processing other tasks + to allow a proper distribution of tasks among all active workers ASAP + """ db = self.db_thread sw = db.scheduler_worker all_active = db( @@ -857,6 +963,11 @@ class Scheduler(MetaScheduler): return False def assign_tasks(self, db): + """Assigns task to workers, that can then pop them from the queue + + Deals with group_name(s) logic, in order to assign linearly tasks + to available workers for those groups + """ sw, st = db.scheduler_worker, db.scheduler_task now = self.now() all_workers = db(sw.status == ACTIVE).select() @@ -934,10 +1045,13 @@ class Scheduler(MetaScheduler): logger.info('TICKER: tasks are %s', x) def sleep(self): + """Calculates the number of seconds to sleep according to worker's + status and `heartbeat` parameter""" time.sleep(self.heartbeat * self.worker_status[1]) # should only sleep until next available task def set_worker_status(self, group_names=None, action=ACTIVE): + """Internal function to set worker's status""" if not group_names: group_names = self.group_names elif isinstance(group_names, str): @@ -948,32 +1062,47 @@ class Scheduler(MetaScheduler): ).update(status=action) def disable(self, group_names=None): + """Sets DISABLED on the workers processing `group_names` tasks. + A DISABLED worker will be kept alive but it won't be able to process + any waiting tasks, essentially putting it to sleep. + By default, all group_names of Scheduler's instantation are selected""" self.set_worker_status(group_names=group_names,action=DISABLED) def resume(self, group_names=None): + """Wakes a worker up (it will be able to process queued tasks)""" self.set_worker_status(group_names=group_names,action=ACTIVE) def terminate(self, group_names=None): + """Sets TERMINATE as worker status. The worker will wait for any + currently running tasks to be executed and then it will exit gracefully + """ self.set_worker_status(group_names=group_names,action=TERMINATE) def kill(self, group_names=None): + """Sets KILL as worker status. The worker will be killed even if it's + processing a task.""" self.set_worker_status(group_names=group_names,action=KILL) def queue_task(self, function, pargs=[], pvars={}, **kwargs): """ Queue tasks. This takes care of handling the validation of all - values. - :param function: the function (anything callable with a __name__) - :param pargs: "raw" args to be passed to the function. Automatically - jsonified. - :param pvars: "raw" kwargs to be passed to the function. Automatically - jsonified - :param kwargs: all the scheduler_task columns. args and vars here should be - in json format already, they will override pargs and pvars + parameters - returns a dict just as a normal validate_and_insert, plus a uuid key holding - the uuid of the queued task. If validation is not passed, both id and uuid - will be None, and you'll get an "error" dict holding the errors found. + Args: + function: the function (anything callable with a __name__) + pargs: "raw" args to be passed to the function. Automatically + jsonified. + pvars: "raw" kwargs to be passed to the function. Automatically + jsonified + kwargs: all the parameters available (basically, every + `scheduler_task` column). If args and vars are here, they should + be jsonified already, and they will override pargs and pvars + + Returns: + a dict just as a normal validate_and_insert(), plus a uuid key + holding the uuid of the queued task. If validation is not passed + ( i.e. some parameters are invalid) both id and uuid will be None, + and you'll get an "error" dict holding the errors found. """ if hasattr(function, '__name__'): function = function.__name__ @@ -999,17 +1128,23 @@ class Scheduler(MetaScheduler): def task_status(self, ref, output=False): """ - Shortcut for task status retrieval + Retrieves task status and optionally the result of the task - :param ref: can be - - integer --> lookup will be done by scheduler_task.id - - string --> lookup will be done by scheduler_task.uuid - - query --> lookup as you wish (as in db.scheduler_task.task_name == 'test1') - :param output: fetch also the scheduler_run record + Args: + ref: can be - Returns a single Row object, for the last queued task - If output == True, returns also the last scheduler_run record - scheduler_run record is fetched by a left join, so it can + - an integer : lookup will be done by scheduler_task.id + - a string : lookup will be done by scheduler_task.uuid + - a `Query` : lookup as you wish, e.g. :: + + db.scheduler_task.task_name == 'test1' + + output(bool): if `True`, fetch also the scheduler_run record + + Returns: + a single Row object, for the last queued task. + If output == True, returns also the last scheduler_run record. + The scheduler_run record is fetched by a left join, so it can have all fields == None """ @@ -1044,21 +1179,27 @@ class Scheduler(MetaScheduler): return row def stop_task(self, ref): - """ - Experimental!!! - Shortcut for task termination. - If the task is RUNNING it will terminate it --> execution will be set as FAILED - If the task is QUEUED, its stop_time will be set as to "now", - the enabled flag will be set to False, status to STOPPED + """Shortcut for task termination. + + If the task is RUNNING it will terminate it, meaning that status + will be set as FAILED. + + If the task is QUEUED, its stop_time will be set as to "now", + the enabled flag will be set to False, and the status to STOPPED + + Args: + ref: can be + + - an integer : lookup will be done by scheduler_task.id + - a string : lookup will be done by scheduler_task.uuid - :param ref: can be - - integer --> lookup will be done by scheduler_task.id - - string --> lookup will be done by scheduler_task.uuid Returns: - 1 if task was stopped (meaning an update has been done) - None if task was not found, or if task was not RUNNING or QUEUED + + Note: + Experimental """ - from gluon.dal import Query st, sw = self.db.scheduler_task, self.db.scheduler_worker if isinstance(ref, int): q = st.id == ref @@ -1080,7 +1221,10 @@ class Scheduler(MetaScheduler): def main(): """ - allows to run worker without python web2py.py .... by simply python this.py + allows to run worker without python web2py.py .... by simply:: + + python gluon/scheduler.py + """ parser = optparse.OptionParser() parser.add_option( diff --git a/gluon/settings.py b/gluon/settings.py index a8c434d9..f0ad1d83 100644 --- a/gluon/settings.py +++ b/gluon/settings.py @@ -1,7 +1,9 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- """ -This file is part of the web2py Web Framework -Copyrighted by Massimo Di Pierro -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) """ import os diff --git a/gluon/shell.py b/gluon/shell.py index 0ced4c25..a7e8b22f 100644 --- a/gluon/shell.py +++ b/gluon/shell.py @@ -2,11 +2,13 @@ # -*- coding: utf-8 -*- """ -This file is part of the web2py Web Framework -Developed by Massimo Di Pierro , -limodou and srackham . -License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) +| This file is part of the web2py Web Framework +| Developed by Massimo Di Pierro , +| limodou and srackham . +| License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) +Web2py environment in the shell +-------------------------------- """ import os @@ -56,17 +58,13 @@ def exec_environment( response=None, session=None, ): - """ - .. function:: gluon.shell.exec_environment([pyfile=''[, request=Request() - [, response=Response[, session=Session()]]]]) + """Environment builder and module loader. - Environment builder and module loader. + Builds a web2py environment and optionally executes a Python file into + the environment. - - Builds a web2py environment and optionally executes a Python - file into the environment. - A Storage dictionary containing the resulting environment is returned. - The working directory must be web2py root -- this is the web2py default. + A Storage dictionary containing the resulting environment is returned. + The working directory must be web2py root -- this is the web2py default. """ @@ -103,17 +101,15 @@ def env( extra_request={}, ): """ - Return web2py execution environment for application (a), controller (c), + Returns web2py execution environment for application (a), controller (c), function (f). If import_models is True the exec all application models into the environment. - extra_request allows you to pass along any extra - variables to the request object before your models - get executed. This was mainly done to support - web2py_utils.test_runner, however you can use it - with any wrapper scripts that need access to the - web2py environment. + extra_request allows you to pass along any extra variables to the request + object before your models get executed. This was mainly done to support + web2py_utils.test_runner, however you can use it with any wrapper scripts + that need access to the web2py environment. """ request = Request({}) @@ -197,8 +193,8 @@ def run( Start interactive shell or run Python script (startfile) in web2py controller environment. appname is formatted like: - a web2py application name - a/c exec the controller c into the application environment + - a : web2py application name + - a/c : exec the controller c into the application environment """ (a, c, f, args, vars) = parse_path_info(appname, av=True) @@ -323,8 +319,8 @@ def run( def parse_path_info(path_info, av=False): """ - Parse path info formatted like a/c/f where c and f are optional - and a leading / accepted. + Parses path info formatted like a/c/f where c and f are optional + and a leading `/` is accepted. Return tuple (a, c, f). If invalid path_info a is set to None. If c or f are omitted they are set to None. If av=True, parse args and vars @@ -358,9 +354,9 @@ def test(testpath, import_models=True, verbose=False): """ Run doctests in web2py environment. testpath is formatted like: - a tests all controllers in application a - a/c tests controller c in application a - a/c/f test function f in controller c, application a + - a: tests all controllers in application a + - a/c: tests controller c in application a + - a/c/f test function f in controller c, application a Where a, c and f are application, controller and function names respectively. If the testpath is a file name the file is tested. diff --git a/gluon/sql.py b/gluon/sql.py index 144e6c27..1b8f801c 100644 --- a/gluon/sql.py +++ b/gluon/sql.py @@ -1,5 +1,15 @@ -# this file exists for backward compatibility +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +| This file is part of the web2py Web Framework +| Developed by Massimo Di Pierro , +| limodou and srackham . +| License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) + +Just for backward compatibility +-------------------------------- +""" __all__ = ['DAL', 'Field', 'DRIVERS'] from dal import DAL, Field, Table, Query, Set, Expression, Row, Rows, DRIVERS, BaseAdapter, SQLField, SQLTable, SQLXorable, SQLQuery, SQLSet, SQLRows, SQLStorage, SQLDB, GQLDB, SQLALL, SQLCustomType diff --git a/gluon/sqlhtml.py b/gluon/sqlhtml.py index e4a1b14a..c832fe9b 100644 --- a/gluon/sqlhtml.py +++ b/gluon/sqlhtml.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) Holds: @@ -13,6 +13,7 @@ Holds: - form_factory: provides a SQLFORM for an non-db backed table """ + import os from gluon.http import HTTP from gluon.html import XmlComponent @@ -100,8 +101,8 @@ def show_if(cond): class FormWidget(object): """ - helper for SQLFORM to generate form input fields - (widget), related to the fieldtype + Helper for SQLFORM to generate form input fields (widget), related to the + fieldtype """ _class = 'generic-widget' @@ -110,12 +111,12 @@ class FormWidget(object): def _attributes(cls, field, widget_attributes, **attributes): """ - helper to build a common set of attributes + Helper to build a common set of attributes - :param field: the field involved, - some attributes are derived from this - :param widget_attributes: widget related attributes - :param attributes: any other supplied attributes + Args: + field: the field involved, some attributes are derived from this + widget_attributes: widget related attributes + attributes: any other supplied attributes """ attr = dict( _id='%s_%s' % (field.tablename, field.name), @@ -135,7 +136,7 @@ class FormWidget(object): @classmethod def widget(cls, field, value, **attributes): """ - generates the widget for the field. + Generates the widget for the field. When serialized, will provide an INPUT tag: @@ -143,9 +144,10 @@ class FormWidget(object): - class = field.type - name = fieldname - :param field: the field needing the widget - :param value: value - :param attributes: any other attributes to be applied + Args: + field: the field needing the widget + value: value + attributes: any other attributes to be applied """ raise NotImplementedError @@ -157,9 +159,9 @@ class StringWidget(FormWidget): @classmethod def widget(cls, field, value, **attributes): """ - generates an INPUT text tag. + Generates an INPUT text tag. - see also: :meth:`FormWidget.widget` + see also: `FormWidget.widget` """ default = dict( @@ -199,9 +201,9 @@ class TextWidget(FormWidget): @classmethod def widget(cls, field, value, **attributes): """ - generates a TEXTAREA tag. + Generates a TEXTAREA tag. - see also: :meth:`FormWidget.widget` + see also: `FormWidget.widget` """ default = dict(value=value) @@ -214,9 +216,9 @@ class JSONWidget(FormWidget): @classmethod def widget(cls, field, value, **attributes): """ - generates a TEXTAREA for JSON notation. + Generates a TEXTAREA for JSON notation. - see also: :meth:`FormWidget.widget` + see also: `FormWidget.widget` """ if not isinstance(value, basestring): if value is not None: @@ -231,9 +233,9 @@ class BooleanWidget(FormWidget): @classmethod def widget(cls, field, value, **attributes): """ - generates an INPUT checkbox tag. + Generates an INPUT checkbox tag. - see also: :meth:`FormWidget.widget` + see also: `FormWidget.widget` """ default = dict(_type='checkbox', value=value) @@ -247,10 +249,13 @@ class OptionsWidget(FormWidget): @staticmethod def has_options(field): """ - checks if the field has selectable options + Checks if the field has selectable options - :param field: the field needing checking - :returns: True if the field has options + Args: + field: the field needing checking + + Returns: + True if the field has options """ return hasattr(field.requires, 'options') @@ -258,9 +263,9 @@ class OptionsWidget(FormWidget): @classmethod def widget(cls, field, value, **attributes): """ - generates a SELECT tag, including OPTIONs (only 1 option allowed) + Generates a SELECT tag, including OPTIONs (only 1 option allowed) - see also: :meth:`FormWidget.widget` + see also: `FormWidget.widget` """ default = dict(value=value) attr = cls._attributes(field, default, @@ -307,12 +312,13 @@ class MultipleOptionsWidget(OptionsWidget): @classmethod def widget(cls, field, value, size=5, **attributes): """ - generates a SELECT tag, including OPTIONs (multiple options allowed) + Generates a SELECT tag, including OPTIONs (multiple options allowed) - see also: :meth:`FormWidget.widget` + see also: `FormWidget.widget` - :param size: optional param (default=5) to indicate how many rows must - be shown + Args: + size: optional param (default=5) to indicate how many rows must + be shown """ attributes.update(_size=size, _multiple=True) @@ -325,9 +331,9 @@ class RadioWidget(OptionsWidget): @classmethod def widget(cls, field, value, **attributes): """ - generates a TABLE tag, including INPUT radios (only 1 option allowed) + Generates a TABLE tag, including INPUT radios (only 1 option allowed) - see also: :meth:`FormWidget.widget` + see also: `FormWidget.widget` """ if isinstance(value, (list,tuple)): @@ -389,9 +395,9 @@ class CheckboxesWidget(OptionsWidget): @classmethod def widget(cls, field, value, **attributes): """ - generates a TABLE tag, including INPUT checkboxes (multiple allowed) + Generates a TABLE tag, including INPUT checkboxes (multiple allowed) - see also: :meth:`FormWidget.widget` + see also: `FormWidget.widget` """ # was values = re.compile('[\w\-:]+').findall(str(value)) @@ -466,11 +472,11 @@ class PasswordWidget(FormWidget): @classmethod def widget(cls, field, value, **attributes): """ - generates a INPUT password tag. + Generates a INPUT password tag. If a value is present it will be shown as a number of '*', not related to the length of the actual value. - see also: :meth:`FormWidget.widget` + see also: `FormWidget.widget` """ # detect if attached a IS_STRONG with entropy default = dict( @@ -506,11 +512,15 @@ class UploadWidget(FormWidget): Optionally provides an A link to the file, including a checkbox so the file can be deleted. + All is wrapped in a DIV. - see also: :meth:`FormWidget.widget` + see also: `FormWidget.widget` - :param download_url: Optional URL to link to the file (default = None) + Args: + field: the field + value: the field value + download_url: url for the file download (default = None) """ default = dict(_type='file',) @@ -554,15 +564,16 @@ class UploadWidget(FormWidget): @classmethod def represent(cls, field, value, download_url=None): """ - how to represent the file: + How to represent the file: - with download url and if it is an image: - otherwise with download url: file - otherwise: file - :param field: the field - :param value: the field value - :param download_url: url for the file download (default = None) + Args: + field: the field + value: the field value + download_url: url for the file download (default = None) """ inp = current.T(cls.GENERIC_DESCRIPTION) @@ -586,7 +597,8 @@ class UploadWidget(FormWidget): Checking is based on filename extension. Currently recognized: gif, png, jp(e)g, bmp - :param value: filename + Args: + value: filename """ extension = value.split('.')[-1].lower() @@ -705,7 +717,7 @@ class AutocompleteWidget(object): def formstyle_table3cols(form, fields): - ''' 3 column table - default ''' + """ 3 column table - default """ table = TABLE() for id, label, controls, help in fields: _help = TD(help, _class='w2p_fc') @@ -716,7 +728,7 @@ def formstyle_table3cols(form, fields): def formstyle_table2cols(form, fields): - ''' 2 column table ''' + """ 2 column table """ table = TABLE() for id, label, controls, help in fields: _help = TD(help, _class='w2p_fc', _width='50%') @@ -728,7 +740,7 @@ def formstyle_table2cols(form, fields): def formstyle_divs(form, fields): - ''' divs only ''' + """ divs only """ table = FIELDSET() for id, label, controls, help in fields: _help = DIV(help, _class='w2p_fc') @@ -739,7 +751,7 @@ def formstyle_divs(form, fields): def formstyle_inline(form, fields): - ''' divs only ''' + """ divs only, but inline """ if len(fields) != 2: raise RuntimeError("Not possible") id, label, controls, help = fields[0] @@ -749,7 +761,7 @@ def formstyle_inline(form, fields): def formstyle_ul(form, fields): - ''' unordered list ''' + """ unordered list """ table = UL() for id, label, controls, help in fields: _help = DIV(help, _class='w2p_fc') @@ -760,7 +772,7 @@ def formstyle_ul(form, fields): def formstyle_bootstrap(form, fields): - ''' bootstrap format form layout ''' + """ bootstrap 2.3.x format form layout """ form.add_class('form-horizontal') parent = FIELDSET() for id, label, controls, help in fields: @@ -804,7 +816,11 @@ def formstyle_bootstrap(form, fields): return parent def formstyle_bootstrap3(form, fields): - ''' bootstrap 3 format form layout ''' + """ bootstrap 3 format form layout + + Note: + Experimental! + """ form.add_class('form-horizontal') parent = FIELDSET() for id, label, controls, help in fields: @@ -862,43 +878,51 @@ def formstyle_bootstrap3(form, fields): class SQLFORM(FORM): """ - SQLFORM is used to map a table (and a current record) into an HTML form + SQLFORM is used to map a table (and a current record) into an HTML form. - given a SQLTable stored in db.table + Given a Table like db.table - generates an insert form:: + Generates an insert form:: SQLFORM(db.table) - generates an update form:: + Generates an update form:: record=db.table[some_id] SQLFORM(db.table, record) - generates an update with a delete button:: + Generates an update with a delete button:: SQLFORM(db.table, record, deletable=True) - if record is an int:: - - record=db.table[record] - - optional arguments: - - :param fields: a list of fields that should be placed in the form, - default is all. - :param labels: a dictionary with labels for each field, keys are the field - names. - :param col3: a dictionary with content for an optional third column + Args: + table: `Table` object + record: either an int if the `id` is an int, or the record fetched + from the table + deletable: adds the delete checkbox + linkto: the URL of a controller/function to access referencedby + records + upload: the URL of a controller/function to download an uploaded file + fields: a list of fields that should be placed in the form, + default is all. + labels: a dictionary with labels for each field, keys are the field + names. + col3: a dictionary with content for an optional third column (right of each field). keys are field names. - :param linkto: the URL of a controller/function to access referencedby - records - see controller appadmin.py for examples - :param upload: the URL of a controller/function to download an uploaded file - see controller appadmin.py for examples + submit_button: text to show in the submit button + delete_label: text to show next to the delete checkbox + showid: shows the id of the record + readonly: doesn't allow for any modification + comments: show comments (stored in `col3` or in Field definition) + ignore_rw: overrides readable/writable attributes + record_id: used to create session key against CSRF + formstyle: what to use to generate the form layout + buttons: override buttons as you please (will be also stored in + `form.custom.submit`) + separator: character as separator between labels and inputs any named optional attribute is passed to the
tag - for example _class, _id, _style, _action, _method, etc. + for example _class, _id, _style, _action, _method, etc. """ @@ -987,13 +1011,6 @@ class SQLFORM(FORM): separator=': ', **attributes ): - """ - SQLFORM(db.table, - record=None, - fields=['name'], - labels={'name': 'Your name'}, - linkto=URL(f='table/db/') - """ T = current.T self.ignore_rw = ignore_rw @@ -1298,12 +1315,15 @@ class SQLFORM(FORM): ): """ - similar FORM.accepts but also does insert, update or delete in DAL. - but if detect_record_change == True than: - form.record_changed = False (record is properly validated/submitted) - form.record_changed = True (record cannot be submitted because changed) - elseif detect_record_change == False than: - form.record_changed = None + Similar to `FORM.accepts` but also does insert, update or delete in DAL. + If detect_record_change is `True` than: + + - `form.record_changed = False` (record is properly validated/submitted) + - `form.record_changed = True` (record cannot be submitted because changed) + + If detect_record_change == False than: + + - `form.record_changed = None` """ if keepvalues is None: @@ -1391,10 +1411,10 @@ class SQLFORM(FORM): if self.record_changed and self.detect_record_change: message_onchange = \ kwargs.setdefault("message_onchange", - current.T("A record change was detected. " + - "Consecutive update self-submissions " + - "are not allowed. Try re-submitting or " + - "refreshing the form page.")) + current.T("A record change was detected. " + + "Consecutive update self-submissions " + + "are not allowed. Try re-submitting or " + + "refreshing the form page.")) if message_onchange is not None: current.response.flash = message_onchange return ret @@ -1633,7 +1653,7 @@ class SQLFORM(FORM): @staticmethod def factory(*fields, **attributes): """ - generates a SQLFORM for the given fields. + Generates a SQLFORM for the given fields. Internally will build a non-database based data model to hold the fields. @@ -2047,10 +2067,11 @@ class SQLFORM(FORM): return buttons def linsert(lst, i, x): - """ - a = [1,2] - linsert(a, 1, [0,3]) - a = [1, 0, 3, 2] + """Internal use only: inserts x list into lst at i pos:: + + a = [1,2] + linsert(a, 1, [0,3]) + a = [1, 0, 3, 2] """ lst[i:i] = x @@ -2455,7 +2476,6 @@ class SQLFORM(FORM): cols = [COL(_id=str(c).replace('.', '-'), data={'column': left_cols + i + 1}) for i,c in enumerate(columns)] - n = len(head.components) cols = [COL(data={'column': i + 1}) for i in range(left_cols)] + \ cols + \ [COL(data={'column': left_cols + len(cols) + i + 1}) @@ -2636,33 +2656,36 @@ class SQLFORM(FORM): divider='>', breadcrumbs_class='', **kwargs): """ - @auth.requires_login() - def index(): - db.define_table('person',Field('name'),format='%(name)s') - db.define_table('dog', - Field('name'),Field('owner',db.person),format='%(name)s') - db.define_table('comment',Field('body'),Field('dog',db.dog)) - if db(db.person).isempty(): - from gluon.contrib.populate import populate - populate(db.person,300) - populate(db.dog,300) - populate(db.comment,1000) - db.commit() - form=SQLFORM.smartgrid(db[request.args(0) or 'person']) #*** - return dict(form=form) + Builds a system of SQLFORM.grid(s) between any referenced tables - *** builds a complete interface to navigate all tables links - to the request.args(0) - table: pagination, search, view, edit, delete, - children, parent, etc. + Args: + table: main table + constraints(dict): `{'table':query}` that limits which records can + be accessible + links(dict): like `{'tablename':[lambda row: A(....), ...]}` that + will add buttons when table tablename is displayed + linked_tables(list): list of tables to be linked + + Example: + given you defined a model as:: + + db.define_table('person',Field('name'),format='%(name)s') + db.define_table('dog', + Field('name'),Field('owner',db.person),format='%(name)s') + db.define_table('comment',Field('body'),Field('dog',db.dog)) + if db(db.person).isempty(): + from gluon.contrib.populate import populate + populate(db.person,300) + populate(db.dog,300) + populate(db.comment,1000) + + in a controller, you can do:: + + @auth.requires_login() + def index(): + form=SQLFORM.smartgrid(db[request.args(0) or 'person']) + return dict(form=form) - constraints is a dict {'table':query} that limits which - records can be accessible - links is a dict like - {'tablename':[lambda row: A(....), ...]} - that will add buttons when table tablename is displayed - linked_tables is a optional list of tablenames of tables - to be linked """ request, T = current.request, current.T if args is None: @@ -2829,76 +2852,39 @@ class SQLFORM(FORM): class SQLTABLE(TABLE): """ - given a Rows object, as returned by a db().select(), generates + Given with a Rows object, as returned by a `db().select()`, generates an html table with the rows. - optional arguments: - - :param linkto: URL (or lambda to generate a URL) to edit individual records - :param upload: URL to download uploaded files - :param orderby: Add an orderby link to column headers. - :param headers: dictionary of headers to headers redefinions - headers can also be a string to gerenare the headers from data - for now only headers="fieldname:capitalize", - headers="labels" and headers=None are supported - :param truncate: length at which to truncate text in table cells. - Defaults to 16 characters. - :param columns: a list or dict contaning the names of the columns to be shown - Defaults to all - - Optional names attributes for passed to the tag - - The keys of headers and columns must be of the form "tablename.fieldname" - - Simple linkto example:: - - rows = db.select(db.sometable.ALL) - table = SQLTABLE(rows, linkto='someurl') - - This will link rows[id] to .../sometable/value_of_id + Args: + sqlrows : the `Rows` object + linkto: URL (or lambda to generate a URL) to edit individual records + upload: URL to download uploaded files + orderby: Add an orderby link to column headers. + headers: dictionary of headers to headers redefinions + headers can also be a string to gerenare the headers from data + for now only headers="fieldname:capitalize", + headers="labels" and headers=None are supported + truncate: length at which to truncate text in table cells. + Defaults to 16 characters. + columns: a list or dict contaning the names of the columns to be shown + Defaults to all + th_link: base link to support orderby headers + extracolumns: a list of dicts + selectid: The id you want to select + renderstyle: Boolean render the style with the table + cid: use this cid for all links + colgroup: #FIXME - More advanced linkto example:: + Extracolumns example + :: - def mylink(field, type, ref): - return URL(args=[field]) + [{'label':A('Extra',_href='#'), + 'class': '', #class name of the header + 'width':'', #width in pixels or % + 'content':lambda row, rc: A('Edit',_href='edit/%s'%row.id), + 'selected': False #agregate class selected to this column}] - rows = db.select(db.sometable.ALL) - table = SQLTABLE(rows, linkto=mylink) - - This will link rows[id] to - current_app/current_controlle/current_function/value_of_id - - New Implements: 24 June 2011: - ----------------------------- - - :param selectid: The id you want to select - :param renderstyle: Boolean render the style with the table - - :param extracolumns = [{'label':A('Extra',_href='#'), - 'class': '', #class name of the header - 'width':'', #width in pixels or % - 'content':lambda row, rc: A('Edit',_href='edit/%s'%row.id), - 'selected': False #agregate class selected to this column - }] - - - :param headers = {'table.id':{'label':'Id', - 'class':'', #class name of the header - 'width':'', #width in pixels or % - 'truncate': 16, #truncate the content to... - 'selected': False #agregate class selected to this column - }, - 'table.myfield':{'label':'My field', - 'class':'', #class name of the header - 'width':'', #width in pixels or % - 'truncate': 16, #truncate the content to... - 'selected': False #agregate class selected to this column - }, - } - - table = SQLTABLE(rows, headers=headers, extracolumns=extracolumns) -`< """ @@ -2948,7 +2934,7 @@ class SQLTABLE(TABLE): cols += [COL(data={'column': len(cols) + i + 1}) for i, c in enumerate(extracolumns)] components.append(COLGROUP(*cols)) - + if headers is None: headers = {} else: @@ -3099,7 +3085,7 @@ class SQLTABLE(TABLE): def style(self): - css = ''' + css = """ table tbody tr.w2p_odd { background-color: #DFD; } @@ -3115,7 +3101,7 @@ class SQLTABLE(TABLE): table tbody tr:hover { background: #DDF; } - ''' + """ return css @@ -3133,7 +3119,8 @@ class ExportClass(object): def represented(self): def none_exception(value): """ - returns a cleaned up value that can be used for csv export: + Returns a cleaned up value that can be used for csv export: + - unicode text is encoded as such - None values are replaced with the given representation (default ) """ diff --git a/gluon/storage.py b/gluon/storage.py index f78df86f..89c88dca 100644 --- a/gluon/storage.py +++ b/gluon/storage.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) Provides: @@ -25,6 +25,8 @@ class Storage(dict): A Storage object is like a dictionary except `obj.foo` can be used in addition to `obj['foo']`, and setting obj.foo = None deletes item foo. + Example:: + >>> o = Storage(a=1) >>> print o.a 1 @@ -39,6 +41,7 @@ class Storage(dict): >>> del o.a >>> print o.a None + """ __slots__ = () __setattr__ = dict.__setitem__ @@ -52,23 +55,25 @@ class Storage(dict): def getlist(self, key): """ - Return a Storage value as a list. + Returns a Storage value as a list. If the value is a list it will be returned as-is. If object is None, an empty list will be returned. - Otherwise, [value] will be returned. + Otherwise, `[value]` will be returned. + + Example output for a query string of `?x=abc&y=abc&y=def`:: + + >>> request = Storage() + >>> request.vars = Storage() + >>> request.vars.x = 'abc' + >>> request.vars.y = ['abc', 'def'] + >>> request.vars.getlist('x') + ['abc'] + >>> request.vars.getlist('y') + ['abc', 'def'] + >>> request.vars.getlist('z') + [] - Example output for a query string of ?x=abc&y=abc&y=def - >>> request = Storage() - >>> request.vars = Storage() - >>> request.vars.x = 'abc' - >>> request.vars.y = ['abc', 'def'] - >>> request.vars.getlist('x') - ['abc'] - >>> request.vars.getlist('y') - ['abc', 'def'] - >>> request.vars.getlist('z') - [] """ value = self.get(key, []) if value is None or isinstance(value, (list, tuple)): @@ -78,43 +83,48 @@ class Storage(dict): def getfirst(self, key, default=None): """ - Return the first or only value when given a request.vars-style key. + Returns the first value of a list or the value itself when given a + `request.vars` style key. If the value is a list, its first item will be returned; otherwise, the value will be returned as-is. - Example output for a query string of ?x=abc&y=abc&y=def - >>> request = Storage() - >>> request.vars = Storage() - >>> request.vars.x = 'abc' - >>> request.vars.y = ['abc', 'def'] - >>> request.vars.getfirst('x') - 'abc' - >>> request.vars.getfirst('y') - 'abc' - >>> request.vars.getfirst('z') + Example output for a query string of `?x=abc&y=abc&y=def`:: + + >>> request = Storage() + >>> request.vars = Storage() + >>> request.vars.x = 'abc' + >>> request.vars.y = ['abc', 'def'] + >>> request.vars.getfirst('x') + 'abc' + >>> request.vars.getfirst('y') + 'abc' + >>> request.vars.getfirst('z') + """ values = self.getlist(key) return values[0] if values else default def getlast(self, key, default=None): """ - Returns the last or only single value when - given a request.vars-style key. + Returns the last value of a list or value itself when given a + `request.vars` style key. If the value is a list, the last item will be returned; otherwise, the value will be returned as-is. - Simulated output with a query string of ?x=abc&y=abc&y=def - >>> request = Storage() - >>> request.vars = Storage() - >>> request.vars.x = 'abc' - >>> request.vars.y = ['abc', 'def'] - >>> request.vars.getlast('x') - 'abc' - >>> request.vars.getlast('y') - 'def' - >>> request.vars.getlast('z') + Simulated output with a query string of `?x=abc&y=abc&y=def`:: + + >>> request = Storage() + >>> request.vars = Storage() + >>> request.vars.x = 'abc' + >>> request.vars.y = ['abc', 'def'] + >>> request.vars.getlast('x') + 'abc' + >>> request.vars.getlast('y') + 'def' + >>> request.vars.getlast('z') + """ values = self.getlist(key) return values[-1] if values else default @@ -124,7 +134,7 @@ PICKABLE = (str, int, long, float, bool, list, dict, tuple, set) class StorageList(Storage): """ - like Storage but missing elements default to [] instead of None + Behaves like Storage but missing elements defaults to [] instead of None """ def __getitem__(self, key): return self.__getattr__(key) @@ -183,35 +193,36 @@ class FastStorage(dict): Eventually this should replace class Storage but causes memory leak because of http://bugs.python.org/issue1469629 - >>> s = FastStorage() - >>> s.a = 1 - >>> s.a - 1 - >>> s['a'] - 1 - >>> s.b - >>> s['b'] - >>> s['b']=2 - >>> s['b'] - 2 - >>> s.b - 2 - >>> isinstance(s,dict) - True - >>> dict(s) - {'a': 1, 'b': 2} - >>> dict(FastStorage(s)) - {'a': 1, 'b': 2} - >>> import pickle - >>> s = pickle.loads(pickle.dumps(s)) - >>> dict(s) - {'a': 1, 'b': 2} - >>> del s.b - >>> del s.a - >>> s.a - >>> s.b - >>> s['a'] - >>> s['b'] + >>> s = FastStorage() + >>> s.a = 1 + >>> s.a + 1 + >>> s['a'] + 1 + >>> s.b + >>> s['b'] + >>> s['b']=2 + >>> s['b'] + 2 + >>> s.b + 2 + >>> isinstance(s,dict) + True + >>> dict(s) + {'a': 1, 'b': 2} + >>> dict(FastStorage(s)) + {'a': 1, 'b': 2} + >>> import pickle + >>> s = pickle.loads(pickle.dumps(s)) + >>> dict(s) + {'a': 1, 'b': 2} + >>> del s.b + >>> del s.a + >>> s.a + >>> s.b + >>> s['a'] + >>> s['b'] + """ def __init__(self, *args, **kwargs): dict.__init__(self, *args, **kwargs) @@ -246,14 +257,30 @@ class FastStorage(dict): class List(list): """ - Like a regular python list but a[i] if i is out of bounds return None - instead of IndexOutOfBounds + Like a regular python list but a[i] if i is out of bounds returns None + instead of `IndexOutOfBounds` """ def __call__(self, i, default=DEFAULT, cast=None, otherwise=None): - """ - request.args(0,default=0,cast=int,otherwise='http://error_url') - request.args(0,default=0,cast=int,otherwise=lambda:...) + """Allows to use a special syntax for fast-check of `request.args()` + validity + + Args: + i: index + default: use this value if arg not found + cast: type cast + otherwise: can be: + + - None: results in a 404 + - str: redirect to this address + - callable: calls the function (nothing is passed) + + Example: + You can use:: + + request.args(0,default=0,cast=int,otherwise='http://error_url') + request.args(0,default=0,cast=int,otherwise=lambda:...) + """ n = len(self) if 0 <= i < n or -n <= i < 0: diff --git a/gluon/streamer.py b/gluon/streamer.py index 25fb8034..700ac41f 100644 --- a/gluon/streamer.py +++ b/gluon/streamer.py @@ -2,9 +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) + +Facilities to handle file streaming +------------------------------------ """ import os diff --git a/gluon/template.py b/gluon/template.py index c148a729..05cb882f 100644 --- a/gluon/template.py +++ b/gluon/template.py @@ -2,16 +2,16 @@ # -*- coding: utf-8 -*- """ -This file is part of the web2py Web Framework (Copyrighted, 2007-2011). -License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) +| This file is part of the web2py Web Framework +| License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) +| Author: Thadeus Burgess +| Contributors: +| - Massimo Di Pierro for creating the original gluon/template.py +| - Jonathan Lundell for extensively testing the regex on Jython. +| - Limodou (creater of uliweb) who inspired the block-element support for web2py. -Author: Thadeus Burgess - -Contributors: - -- Thank you to Massimo Di Pierro for creating the original gluon/template.py -- Thank you to Jonathan Lundell for extensively testing the regex on Jython. -- Thank you to Limodou (creater of uliweb) who inspired the block-element support for web2py. +Templating syntax +------------------ """ import os @@ -90,6 +90,7 @@ class BlockNode(Node): {{ block test }} This is default block test {{ end }} + """ def __init__(self, name='', pre_extend=False, delimiters=('{{', '}}')): """ @@ -115,11 +116,10 @@ class BlockNode(Node): def append(self, node): """ - Add an element to the nodes. + Adds an element to the nodes. - Keyword Arguments - - - node -- Node object or string to append. + Args: + node: Node object or string to append. """ if isinstance(node, str) or isinstance(node, Node): self.nodes.append(node) @@ -128,11 +128,10 @@ class BlockNode(Node): def extend(self, other): """ - Extend the list of nodes with another BlockNode class. + Extends the list of nodes with another BlockNode class. - Keyword Arguments - - - other -- BlockNode or Content object to extend from. + Args: + other: BlockNode or Content object to extend from. """ if isinstance(other, BlockNode): self.nodes.extend(other.nodes) @@ -143,8 +142,9 @@ class BlockNode(Node): def output(self, blocks): """ Merges all nodes into a single string. - blocks -- Dictionary of blocks that are extending - from this template. + + Args: + blocks: Dictionary of blocks that are extending from this template. """ return ''.join(output_aux(node, blocks) for node in self.nodes) @@ -154,13 +154,11 @@ class Content(BlockNode): Parent Container -- Used as the root level BlockNode. Contains functions that operate as such. + + Args: + name: Unique name for this BlockNode """ def __init__(self, name="ContentBlock", pre_extend=False): - """ - Keyword Arguments - - name -- Unique name for this BlockNode - """ self.name = name self.nodes = [] self.blocks = {} @@ -220,6 +218,21 @@ class Content(BlockNode): class TemplateParser(object): + """Parse all blocks + + Args: + text: text to parse + context: context to parse in + path: folder path to templates + writer: string of writer class to use + lexers: dict of custom lexers to use. + delimiters: for example `('{{','}}')` + _super_nodes: a list of nodes to check for inclusion + this should only be set by "self.extend" + It contains a list of SuperNodes from a child + template that need to be handled. + + """ default_delimiters = ('{{', '}}') r_tag = compile(r'(\{\{.*?\}\})', DOTALL) @@ -244,18 +257,6 @@ class TemplateParser(object): delimiters=('{{', '}}'), _super_nodes = [], ): - """ - text -- text to parse - context -- context to parse in - path -- folder path to templates - writer -- string of writer class to use - lexers -- dict of custom lexers to use. - delimiters -- for example ('{{','}}') - _super_nodes -- a list of nodes to check for inclusion - this should only be set by "self.extend" - It contains a list of SuperNodes from a child - template that need to be handled. - """ # Keep a root level name. self.name = name @@ -317,18 +318,18 @@ class TemplateParser(object): def to_string(self): """ - Return the parsed template with correct indentation. + Returns the parsed template with correct indentation. Used to make it easier to port to python3. """ return self.reindent(str(self.content)) def __str__(self): - "Make sure str works exactly the same as python 3" + "Makes sure str works exactly the same as python 3" return self.to_string() def __unicode__(self): - "Make sure str works exactly the same as python 3" + "Makes sure str works exactly the same as python 3" return self.to_string() def reindent(self, text): @@ -411,13 +412,13 @@ class TemplateParser(object): def _raise_error(self, message='', text=None): """ - Raise an error using itself as the filename and textual content. + Raises an error using itself as the filename and textual content. """ raise RestrictedError(self.name, text or self.text, message) def _get_file_text(self, filename): """ - Attempt to open ``filename`` and retrieve its text. + Attempts to open ``filename`` and retrieve its text. This will use self.path to search for the file. """ @@ -454,7 +455,7 @@ class TemplateParser(object): def include(self, content, filename): """ - Include ``filename`` here. + Includes ``filename`` here. """ text = self._get_file_text(filename) @@ -469,8 +470,8 @@ class TemplateParser(object): def extend(self, filename): """ - Extend ``filename``. Anything not declared in a block defined by the - parent will be placed in the parent templates ``{{include}}`` block. + Extends `filename`. Anything not declared in a block defined by the + parent will be placed in the parent templates `{{include}}` block. """ # If no filename, create a dummy layout with only an {{include}}. text = self._get_file_text(filename) or '%sinclude%s' % tuple(self.delimiters) @@ -770,9 +771,12 @@ def parse_template(filename, delimiters=('{{', '}}') ): """ - filename can be a view filename in the views folder or an input stream - path is the path of a views folder - context is a dictionary of symbols used to render the template + Args: + filename: can be a view filename in the views folder or an input stream + path: is the path of a views folder + context: is a dictionary of symbols used to render the template + lexers: dict of custom lexers to use + delimiters: opening and closing tags """ # First, if we have a str try to open the file @@ -841,30 +845,44 @@ def render(content="hello world", writer='response.write' ): """ - >>> render() - 'hello world' - >>> render(content='abc') - 'abc' - >>> render(content='abc\'') - "abc'" - >>> render(content='a"\'bc') - 'a"\\'bc' - >>> render(content='a\\nbc') - 'a\\nbc' - >>> render(content='a"bcd"e') - 'a"bcd"e' - >>> render(content="'''a\\nc'''") - "'''a\\nc'''" - >>> render(content="'''a\\'c'''") - "'''a\'c'''" - >>> render(content='{{for i in range(a):}}{{=i}}
{{pass}}', context=dict(a=5)) - '0
1
2
3
4
' - >>> render(content='{%for i in range(a):%}{%=i%}
{%pass%}', context=dict(a=5),delimiters=('{%','%}')) - '0
1
2
3
4
' - >>> render(content="{{='''hello\\nworld'''}}") - 'hello\\nworld' - >>> render(content='{{for i in range(3):\\n=i\\npass}}') - '012' + Generic render function + + Args: + content: default content + stream: file-like obj to read template from + filename: where to find template + path: base path for templates + context: env + lexers: custom lexers to use + delimiters: opening and closing tags + writer: where to inject the resulting stream + + Example:: + >>> render() + 'hello world' + >>> render(content='abc') + 'abc' + >>> render(content="abc'") + "abc'" + >>> render(content=''''a"'bc''') + 'a"'bc' + >>> render(content='a\\nbc') + 'a\\nbc' + >>> render(content='a"bcd"e') + 'a"bcd"e' + >>> render(content="'''a\\nc'''") + "'''a\\nc'''" + >>> render(content="'''a\\'c'''") + "'''a\'c'''" + >>> render(content='{{for i in range(a):}}{{=i}}
{{pass}}', context=dict(a=5)) + '0
1
2
3
4
' + >>> render(content='{%for i in range(a):%}{%=i%}
{%pass%}', context=dict(a=5),delimiters=('{%','%}')) + '0
1
2
3
4
' + >>> render(content="{{='''hello\\nworld'''}}") + 'hello\\nworld' + >>> render(content='{{for i in range(3):\\n=i\\npass}}') + '012' + """ # here to avoid circular Imports try: