Deprecate support for calling native python callbacks.

pull/7/head
Ferry Boender 10 years ago
parent 23fea8f605
commit 55f569fc75
  1. 5
      examples/native/README.md
  2. 64
      examples/native/native.json
  3. 47
      examples/native/native.py
  4. 62
      src/scriptform.py

@ -1,5 +0,0 @@
ScriptForm native example
=========================
This example shows how to create two simple forms with python functions as
backends.

@ -1,64 +0,0 @@
{
"title": "Test server",
"forms": {
"import": {
"title": "Import data",
"description": "Import CSV data into a database",
"submit_title": "Import",
"fields": [
{
"name": "target_db",
"title": "Database to import to",
"type": "select",
"options": [
["devtest", "Dev Test db"],
["prodtest", "Prod Test db"]
]
},
{
"name": "csv_file",
"title": "CSV file",
"type": "file"
}
]
},
"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",
"submit_title": "Add user",
"fields": [
{
"name": "username",
"title": "Username",
"type": "string"
},
{
"name": "password1",
"title": "Password",
"type": "password"
},
{
"name": "password2",
"title": "Password (Repear)",
"type": "password"
}
]
}
}
}

@ -1,47 +0,0 @@
#!/usr/bin/python
import scriptform
import sys
def job_import(values, request):
return "Importing into database '{}'".format(values['target_db'])
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:
raise Exception("Empty password specified")
if password1 != password2:
raise Exception("Passwords do not match.")
# We do some stuff here.
return "User created"
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=8000)

@ -163,15 +163,11 @@ class DaemonError(Exception):
class ScriptForm: class ScriptForm:
""" """
'Main' class that orchestrates parsing the Form configurations, hooking up 'Main' class that orchestrates parsing the Form configurations and running
callbacks and running the webserver. the webserver.
""" """
def __init__(self, config_file, callbacks=None): def __init__(self, config_file):
self.config_file = config_file self.config_file = config_file
if callbacks:
self.callbacks = callbacks
else:
self.callbacks = {}
self.basepath = os.path.realpath(os.path.dirname(config_file)) self.basepath = os.path.realpath(os.path.dirname(config_file))
self.log = logging.getLogger('SCRIPTFORM') self.log = logging.getLogger('SCRIPTFORM')
self.get_form_config() # Init form config so it can raise errors about problems. self.get_form_config() # Init form config so it can raise errors about problems.
@ -190,7 +186,6 @@ class ScriptForm:
config = json.load(file(path, 'r')) config = json.load(file(path, 'r'))
forms = [] forms = []
callbacks = self.callbacks
users = None users = None
if 'users' in config: if 'users' in config:
@ -214,7 +209,6 @@ class ScriptForm:
form_config = FormConfig( form_config = FormConfig(
config['title'], config['title'],
forms, forms,
callbacks,
users users
) )
self.form_config_singleton = form_config self.form_config_singleton = form_config
@ -247,20 +241,15 @@ class FormConfig:
file. It holds information (title, users, the form definitions) on the file. It holds information (title, users, the form definitions) on the
form configuration being served by this instance of ScriptForm. form configuration being served by this instance of ScriptForm.
""" """
def __init__(self, title, forms, callbacks={}, users={}): def __init__(self, title, forms, users={}):
self.title = title self.title = title
self.users = users self.users = users
self.forms = forms self.forms = forms
self.callbacks = callbacks
# Validate scripts # Validate scripts
for form_def in self.forms: for form_def in self.forms:
if form_def.script:
if not stat.S_IXUSR & os.stat(form_def.script)[stat.ST_MODE]: if not stat.S_IXUSR & os.stat(form_def.script)[stat.ST_MODE]:
raise ScriptFormError("{0} is not executable".format(form_def.script)) raise ScriptFormError("{0} is not executable".format(form_def.script))
else:
if not form_def.name in self.callbacks:
raise ScriptFormError("No script or callback registered for '{0}'".format(form_def.name))
def get_form_def(self, form_name): def get_form_def(self, form_name):
""" """
@ -276,30 +265,26 @@ class FormConfig:
def callback(self, form_name, form_values, request): def callback(self, form_name, form_values, request):
""" """
Perform a callback for the form `form_name`. This either calls a script Perform a callback for the form `form_name`. This calls a script.
or a Python callable, depending on how the callback is registered.
`form_values` is a dictionary of validated values as returned by `form_values` is a dictionary of validated values as returned by
FormDefinition.validate(). `request` is the request handler context FormDefinition.validate(). `request` is the request handler context
(ScriptFormWebApp). Scripts and Python callables can use it to send (ScriptFormWebApp). The output of the script is hooked up to the
their responses. output, depending on the output type.
""" """
form = self.get_form_def(form_name) form = self.get_form_def(form_name)
if form.script:
return self.callback_script(form, form_values, request.wfile)
else:
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.
os.chdir(os.path.dirname(form.script)) os.chdir(os.path.dirname(form.script))
# Pass form values to the script through the environment as strings.
env = os.environ.copy() env = os.environ.copy()
for k, v in form_values.items(): for k, v in form_values.items():
env[k] = str(v) env[k] = str(v)
if form.output == 'raw': if form.output == 'raw':
p = subprocess.Popen(form.script, shell=True, stdout=output_fh, p = subprocess.Popen(form.script, shell=True,
stderr=output_fh, env=env) stdout=request.wfile,
stderr=request.wfile,
env=env)
stdout, stderr = p.communicate(input) stdout, stderr = p.communicate(input)
return None return None
else: else:
@ -313,27 +298,6 @@ class FormConfig:
'exitcode': p.returncode 'exitcode': p.returncode
} }
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: class FormDefinition:
""" """
@ -772,7 +736,7 @@ class ScriptFormWebApp(WebAppHandler):
def h_submit(self, form_values): def h_submit(self, form_values):
""" """
Handle the submitting of a form by validating the values and then doing Handle the submitting of a form by validating the values and then doing
a callback to either a script or a Python function. How the output is a callback to a script. How the output is
handled depends on settings in the form definition. handled depends on settings in the form definition.
""" """
form_config = self.scriptform.get_form_config() form_config = self.scriptform.get_form_config()

Loading…
Cancel
Save