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:
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: