1

I have a Python script that continuously makes GET and POST requests to a third-party API at about 1 request per second. It uses a while loop, so the requests all come from the same process. Currently, I just run and kill the script on my local machine with python run.py, but I'd like to be able to start and stop this script from anywhere (e.g. my phone), so I was considering hosting it as a Django app on Heroku. Is this possible?

I've considered a few ways to accomplish this, but I'm not sure any of them would work. One thought would be to have a "run" button on a web page that starts a background process to run the script, but:

  • I wouldn't know how to kill this process from a browser once it was created (heroku ps:stop is less than ideal since I would need a console).
  • I'm not sure that Heroku was intended to have a script running for potentially several days.

If all else fails, I'll probably just end up running the script on a Raspberry Pi and exposing localhost, but it would be convenient to use Heroku to do this.

3
  • could you include code for this?
    – user7433
    Commented Jul 20, 2016 at 18:05
  • I actually figured out a better solution than the one I posted below. I'm going to update that answer, hopefully in the next couple of days. I'll include code with it. Commented Jul 21, 2016 at 1:49
  • @omouse, I've updated my answer, it includes a GitHub repository that demonstrates the method I used. Commented Jul 22, 2016 at 9:38

1 Answer 1

1

UPDATED ANSWER

The best way I've found to control a looping script in Heroku is by defining a process type for the script in the Procfile. This way, you simply have to scale the number of dynos for the process type to 1 and 0 to start and stop the script, respectively. So for a process type named "run":

Start the script

heroku ps:scale run=1

Stop the script

heroku ps:scale run=0

-- Demo --

Here's a demo of a way to easily scale the dynos using a Django application and the Heroku API endpoint for scaling. I made a GitHub repository in case it would help anyone, and I've included the important pieces below:

Procfile

web: gunicorn {project}.wsgi
run: python run.py {args}

run.py

def run():
    # looping code here


if __name__ == '__main__':
    run()

index.html

<label for="run-loop">Loop running: </label>
<input id="run-loop" type="checkbox">

main.js

// set the checkbox to checked if the dyno is up, unchecked otherwise
var getLoopStatus = function() {
    $.ajax({
        url: "/get-loop-status/",
        success: function(data) {
            if (data.quantity > 0) {
                $('#run-loop').prop('checked', 'checked');
            }
        }
    });
};

// make a request to the server to scale the "run" process type
var runLoop = function(quantity) {
    $.ajax({
        url: "/run-loop/",
        method: "POST",
        data: {
            quantity: quantity
        },
        headers: {
            "X-CSRFToken": Cookies.get('csrftoken'),
        }
    });
};


$(function() {
    // bind the checkbox to the runLoop function
    $('#run-loop').change(function() {
        runLoop(+$(this).is(':checked'));
    });

    getLoopStatus();
});

urls.py

urlpatterns = [
    url(r'^$', TemplateView.as_view(template_name='loopapp/index.html'), name='home'),
    url(r'^get-loop-status/$', LoopStatusView.as_view()),
    url(r'^run-loop/$', RunLoopView.as_view()),
    url(r'^admin/', include(admin.site.urls)),
]

views.py

class LoopStatusView(View):
    def get(self, request, *args, **kwargs):
        response = requests.get(HEROKU_RUN_URL, headers=HEROKU_HEADERS)
        return JsonResponse({'quantity': response.json()['quantity']})


class RunLoopView(View):
    def post(self, request, *args, **kwargs):
        data = request.POST.dict()
        payload = RUN_POST_PAYLOAD % data['quantity']
        response = requests.patch(HEROKU_RUN_URL, data=payload, headers=HEROKU_HEADERS)
        return JsonResponse({'new_quantity': response.json()['quantity']})

-- How to find Heroku API URL --

You should have Chrome DevTools open on the "Network" tab for this. Go to your app in your Heroku dashboard and click on the "Resources" tab. Then click the pencil icon next to the looping process:

HerokuAPI1

Then, activate a dyno for that process type by clicking the switch and then clicking "Confirm". The Heroku API endpoint will come up in DevTools in the form of:

https://api.heroku.com/apps/{app ID}/formation/{process-ID}

In order to make requests to this endpoint, you'll need an access token. Expand the request in DevTools and find the Authorization: Bearer {access token} header under the "Request Headers" of the "Headers" tab.

Note: This token expires frequently. You'll probably either want to refresh this token when it expires or use Basic Authentication, which doesn't expire. I had Postman generate a Basic Authentication token for me.


OLD ANSWER

I've found an answer I'm somewhat happy with. I don't think it's possible to have a front-end interface that controls the execution of continuous loops on Heroku, but I did find an app called Nezumi which allows you to run functions and control Heroku processes from your phone.

What I ended up doing was I made a file run.py in my root directory of a Django app and pushed this to Heroku. Then, in Nezumi, I can go click on the "run" icon and type python run.py {args} which creates a "run" process and executes the loop. I can stop the process and view the logs by clicking on the "ps" icon in Nezumi. So, now I can start and stop this script from anywhere I have my phone.

Nezumi app view:

Nezumi

Not the answer you're looking for? Browse other questions tagged or ask your own question.