From e511678c075bb206a57415c038ea320832b14946 Mon Sep 17 00:00:00 2001 From: Ferry Boender Date: Fri, 3 Apr 2015 22:45:42 +0200 Subject: [PATCH] Fix native Python callbacks after refactoring. --- README.md | 10 +++++----- examples/native/native.json | 16 ++++++++++++++++ examples/native/native.py | 28 +++++++++++++++++++++++----- src/scriptform.py | 35 +++++++++++++++++++++++++++-------- 4 files changed, 71 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 7e123a8..dae50e4 100644 --- a/README.md +++ b/README.md @@ -9,8 +9,8 @@ as frontends to scripts. ScriptForm takes a JSON file which contains form definitions. It then constructs web forms from this JSON and serves these to users. The user can select a form and fill it out. When the user submits the form, it is validated -and the associated script is called. Data entered in the form is passed to the -script through the environment. +and the associated script or Python callback is called. Data entered in the +form is passed to the script through the environment. ### Features @@ -22,6 +22,8 @@ script through the environment. - Uploaded files are automatically saved to temporary files, which are passed on to the callback. - Multiple forms in a single JSON definition file. +- Handles script / exception errors, HTML output or lets scripts and Python + callbacks stream their own HTTP response to the browser. ### Use-cases @@ -106,9 +108,7 @@ ScriptForm requires: ## Usage -### Authentication - -Passwords are stored in plain text. +FIXME ## License diff --git a/examples/native/native.json b/examples/native/native.json index 640af06..e2d1faa 100644 --- a/examples/native/native.json +++ b/examples/native/native.json @@ -22,6 +22,22 @@ } ] }, + "export": { + "title": "Export data", + "description": "Export a dump of the database", + "submit_title": "Export", + "fields": [ + { + "name": "source_db", + "title": "Database to export", + "type": "select", + "options": [ + ["devtest", "Dev Test db"], + ["prodtest", "Prod Test db"] + ] + } + ] + }, "add_user": { "title": "Add user", "description": "Add a user to the htaccess file or change their password", diff --git a/examples/native/native.py b/examples/native/native.py index 3416e8f..647a03e 100755 --- a/examples/native/native.py +++ b/examples/native/native.py @@ -1,20 +1,37 @@ #!/usr/bin/python import scriptform +import sys -def job_import(values): +def job_import(values, request): return "Importing into database '{}'".format(values['target_db']) -def job_add_user(values): +def job_export(values, request): + size = 4096 * 10000 + request.wfile.write('HTTP/1.0 200 Ok\n') + request.wfile.write('Content-Type: application/octet-stream\n') + request.wfile.write('Content-Disposition: attachment; filename="large_file.dat"\n') + request.wfile.write('Content-Length: {0}\n\n'.format(size)) + + f = file('/dev/urandom', 'r') + sent_size = 0 + while True: + buf = f.read(4096) + if sent_size >= size: + break + request.wfile.write(buf) + sent_size += 4096 + +def job_add_user(values, request): username = values['username'] password1 = values['password1'] password2 = values['password2'] if not password1: - return "Empty password specified." + raise Exception("Empty password specified") if password1 != password2: - return "Passwords do not match." + raise Exception("Passwords do not match.") # We do some stuff here. @@ -23,7 +40,8 @@ def job_add_user(values): if __name__ == "__main__": callbacks = { 'import': job_import, + 'export': job_export, 'add_user': job_add_user } sf = scriptform.ScriptForm('native.json', callbacks) - sf.run(listen_port=8080) + sf.run(listen_port=8000) diff --git a/src/scriptform.py b/src/scriptform.py index 2499324..7db211f 100755 --- a/src/scriptform.py +++ b/src/scriptform.py @@ -6,6 +6,7 @@ # - Default values for input fields. # - If there are errors in the form, its values are empties. # - Send responses using self.send_ if possible +# - Complain about no registered callback on startup instead of serving. import sys import optparse @@ -214,8 +215,8 @@ class FormConfig: if not stat.S_IXUSR & os.stat(form_def.script)[stat.ST_MODE]: raise ScriptFormError("{0} is not executable".format(form_def.script)) else: - if not form_name in self.callbacks: - raise ScriptFormError("No script or callback registered for '{0}'".format(form_name)) + if not form_def.name in self.callbacks: + raise ScriptFormError("No script or callback registered for '{0}'".format(form_def.name)) def get_form(self, form_name): for form_def in self.forms: @@ -224,12 +225,12 @@ class FormConfig: else: raise ValueError("No such form: {0}".format(form_name)) - def callback(self, form_name, form_values, output_fh=None): + def callback(self, form_name, form_values, request): form = self.get_form(form_name) if form.script: - return self.callback_script(form, form_values, output_fh) + return self.callback_script(form, form_values, request.wfile) else: - return self.callback_python(form, form_values, output_fh) + return self.callback_python(form, form_values, request) def callback_script(self, form, form_values, output_fh=None): # Pass form values to the script through the environment as strings. @@ -253,8 +254,26 @@ class FormConfig: 'exitcode': p.returncode } - def callback_python(self, form, form_values, output_fh=None): - pass + def callback_python(self, form, form_values, request): + callback = self.callbacks[form.name] + + try: + result = callback(form_values, request) + if result: + return { + 'stdout': result, + 'stderr': '', + 'exitcode': 0 + } + else: + # Raw output + pass + except Exception,e : + return { + 'stdout': '', + 'stderr': str(e), + 'exitcode': 1 + } class FormDefinition: @@ -744,7 +763,7 @@ class ScriptFormWebApp(WebAppHandler): # in some nice HTML. If no result is returned, the output was raw # and the callback should have written its own response to the # self.wfile filehandle. - result = form_config.callback(form_name, form_values, self.wfile) + result = form_config.callback(form_name, form_values, self) if result: if result['exitcode'] != 0: msg = '{0}'.format(cgi.escape(result['stderr']))