

Ask HN: How do you share scripts with non-coders? - hackerews

I used to work on a team with analysts who didn&#x27;t know how to code. Typically, I&#x27;d write my own Python or R functions for data cleaning, light analysis, and plotting.<p>Eventually, the other analysts wanted to use my functions for their own projects, so I tried teaching them how to execute Python from the terminal. That didn&#x27;t really work.<p>Then I spent a day building a small app to run my script. It was valuable, but still a lot of work for something so small.<p>Just wondering - what do others do to share scripts with non-coders?
======
glimcat
Set up a lightweight web interface using Flask. When people log in and submit
a job, it gets added to an RQ task queue for asynchronous execution. If they
need anything back at the end, job status, results, whatever - that goes in a
database. Congratulations, you've just made a simple web app.

If people are actually getting use out of it, go back and pretty it up a bit
with a Bootstrap template or whatever, it doesn't take much here to have
meaningful effects on user perception. It mostly just has to be nice enough
that it looks reasonably professional when people show it off in meetings,
which is NOT a cutting-edge design problem.

Add more pages for more scripts as needed, and tag users with permissions so
you know which scripts should be exposed to which users.

If merited, go back and add fancy features like generating PDF reports and
emailing them to the head of department every week.

~~~
hackerews
This is interesting. What if users have to put in their own inputs for the
scripts? For instance, specify start-date and end-date for a sql query, or
upload a spreadsheet to do the python analysis on?

~~~
glimcat
You'd use Flask-WTF for forms.

[https://flask-wtf.readthedocs.org/en/latest/](https://flask-
wtf.readthedocs.org/en/latest/)

It can also handle file uploads.

[https://flask-wtf.readthedocs.org/en/latest/form.html](https://flask-
wtf.readthedocs.org/en/latest/form.html)

You'd need to define a model which describes the form fields, handle it in
your view, and add it to your page template.

forms.py

    
    
        from flask_wtf import Form
        from wtforms import TextField, PasswordField
        from wtforms.validators import DataRequired
        
        class LoginForm(Form):
            email = TextField('Email', validators=[DataRequired()])
            password = PasswordField('Password', validators=[DataRequired()])
    

views.py

    
    
        from forms import LoginForm
        
        @app.route('/login', methods=('GET', 'POST'))
        def login():
            form = LoginForm()
            if form.validate_on_submit():
                email = form.data.get('email', None)
                password = form.data.get('password', None)
                do_login_stuff(email, password)
            else:
                return render_template('login_page.html', form=form)
    

login_page.html

    
    
        {% extends "base.html" %}
    
        {% block content %}
        <h3>Log in:</h3>
        <form method="POST" action="{{ url_for('login') }}">
          {{ form.hidden_tag() }}
          {{ form.email.label }} {{ form.email }}
          {{ form.password.label }} {{ form.password }}
          <input type="submit" value="Log in">
        </form>
        {% endblock %}

~~~
hackerews
Yeah that's the tough part. I think I'd have to set up a model for each script
I add to flask.

~~~
glimcat
Here's an option for organizing a "bunch of scripts" app that might be less
intimidating to maintain.

foo_script.py

    
    
        from flask import render_template
        from flask_wtf import Form
        from wtforms import TextField
        from wtforms.validators import DataRequired
        
        class ScriptForm(Form):
            param1 = TextField('Param1', validators=[DataRequired()])
            param2 = TextField('Param2', validators=[DataRequired()])
            param3 = TextField('Param3', validators=[DataRequired()])
    
        @app.route('/foo-script', methods=('GET', 'POST'))
        def foo_script():
            form = ScriptForm()
            if form.validate_on_submit():
                do_stuff(form.data)
            else:
                return render_template('foo_script.html', form=form)
        
        def do_stuff(data):
            for key in data:
                print '%s: %s' % (key, data[key])
    
    

foo_script.html

    
    
        {% extends "base.html" %}
    
        {% block content %}
        <h3>Run foo script:</h3>
        <form method="POST" action="{{ url_for('foo_script') }}">
          {{ form.hidden_tag() }}
          {{ form.param1.label }} {{ form.param1 }}
          {{ form.param2.label }} {{ form.param2 }}
          {{ form.param3.label }} {{ form.param3 }}
          <input type="submit" value="Run foo script">
        </form>
        {% endblock %}
    
    

base.html

    
    
        <!DOCTYPE html>
        <html lang="en">
          <head>
            <title>FooCorp Script Service</title>
          </head>
          <body>
            {% block content %}{% endblock %}
          </body>
        </html>
    
    

app.py

    
    
        from flask import Flask
        
        app = Flask(__name__)
        app.config.from_pyfile('config.py')
        
        import foo_script, bar_script, baz_script
    
    

config.py

    
    
        # You need this for CSRF protection & cookie signing
        SECRET_KEY = randomly_generated_secret_key
    
    

You should also look into Flask's blueprints at some point if it keeps
growing. But it's not really essential, just another tool to help you keep
projects organized. Flask is mostly "do whatever makes sense in your specific
case" rather than imposing many global constraints on structure.

Also, I'd use gunicorn if you're deploying your own server. It's a bit less
intimidating to get set up than uWSGI, and the main tradeoff is that it
doesn't support quite as many thousands of users (i.e. not relevant).

------
BWStearns
I had a similar situation and I either wrapped them in really simple tkinter
interface or used py2exe. Sometimes I wrote up some dialogues to get user
input to control the script wrap it up in a folder with a .sh file that runs
the python script and just tell them that the .sh file is the program. Double
click, opens program, follow instructions (hacky and fragile but got the job
done).

They were usually limited in scope enough where I just let the script crash on
most errors and just warned the users about it.

~~~
hackerews
I did py2exe too when I was working a consulting gig a few years ago! :)

------
bluerail
I have created a standardized code numbers for the output (like 12 for
success, 13 for suspended, etc..) and the script will output the respective
number as an output of which the list of numbers and their respective output
is with them already..

Then, I ll just Zip it, and send them the scripts along with some step by step
instructions on how to execute it (with screenshots) and the results
expected.. At beginning it will be hassle but on the way most documents will
become common for all...

~~~
hackerews
I've done this too. Takes so long to set up all the instructions though.

------
sudont
At my day job we generally will bundle small procedures/scripts into a simple
webapp with either flask or express.js. For our backend systems, we're looking
into rundeck to allow non sudoers the ability to restart a system service.
That might be potentially useful.

It seems like there might be a niche for a "Delphi for the web" stack of
reusable components and standard backend systems.

~~~
hackerews
This sounds really cool! So non-engineers can just click "run" on a system
service and get the output? How do you handle small procedures/scripts that
require them to put in inputs? Or does that not pop up for you..

------
skratlo
Send them a link to iPython Notebook, either host it yourself or use
[http://nbviewer.ipython.org/](http://nbviewer.ipython.org/) . Should be easy
to teach them to modify the input data and re-run the script from the web UI.

------
markcampbell
Link to zip of gist separated into the files related.

~~~
donpinkus
Is a non-engineer able to understand this?

~~~
markcampbell
It'll be a zip file that you extract -- it'll be in the correct structure.

