Program description: I already have a functioning program that runs on console window, but I'd like to present its output on a locally hosted web page. The program consists on getting lyrics for currently playing songs by making requests to Spotify's API. I store the current lyrics in a "lyrics.txt" file.
What I want:
Change the web page from the running lyrics program when it detects the song has changed.
[EDIT:]
Is there a way to make the flask page display a variable, that is updated by a python request.post of the lyrics app to the flask url with the updated variable as the data?
What I have:
I'm using Flask as the framework since its a one local web page.
import os, io
from flask import Flask, render_template
app = Flask(__name__)
#app.route("/")
def test():
'''reads the current lyrics file and passes it to the web page
manually reload the page to update lyrics'''
with io.open('lyrics.txt', 'r') as f:
HEAD = f.readline().strip("\n")
BODY = f.read().split('\n')
lyrics = {"HEAD": HEAD, "BODY": BODY}
return render_template("home.html", lyrics=lyrics)
if __name__ == "__main__":
app.run(debug=1)
link to lyrics app github
You would need JavaScript/AJAX on page which periodically sends request for new content and Flask should send current content from file.
In this example I use jQuery.get() to send request to server, and setTimeout() to repeat it periodically.
Flask sends current time to show different content.
import datetime
from flask import Flask, render_template_string
app = Flask(__name__)
#app.route("/")
def index():
return render_template_string("""<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Test</title>
<script type="text/javascript" src="http://code.jquery.com/jquery-1.8.0.min.js"></script>
<script type="text/javascript">
function updater() {
$.get('/data', function(data) {
$('#time').html(data); // update page with new data
});
};
setInterval(updater, 1000); // run `updater()` every 1000ms (1s)
</script>
</head>
<body>
Date & Time: <span id="time"><span>
</body>
</html>""")
#app.route('/data')
def data():
"""send current content"""
return datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
if __name__ == "__main__":
app.run(debug=True)
EDIT:
The same using standard fetch() without external libraries.
Code has to be after <span>
import datetime
from flask import Flask, render_template_string
app = Flask(__name__)
#app.route("/")
def index():
return render_template_string("""<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Test</title>
</head>
<body>
Date & Time: <span id="time"><span>
<script type="text/javascript">
var time_span = document.getElementById("time");
function updater() {
fetch('/data')
.then(response => response.text())
.then(text => (time_span.innerHTML = text)); // update page with new data
}
setInterval(updater, 1000); // run `updater()` every 1000ms (1s)
</script>
</body>
</html>""")
#app.route('/data')
def data():
"""send current content"""
return datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
if __name__ == "__main__":
app.run(debug=True)
Related
It appears that Flask assumes that the server is returning html to the client (browser).
Here's a simple example;
import json
from flask import Flask
app = Flask(__name__)
#app.route("/")
def home():
msg = ['Hello, world!']
return json.dumps(msg) + '\n'
This code works as expected and returns the desired json;
$ curl -s http://localhost:5000/
["Hello, world!"]
But if I introduce an error;
import json
from flask import Flask
app = Flask(__name__)
#app.route("/")
def home():
msg = ['Hello, world!']
return json.dumps(XXmsg) + '\n'
Then Flask emits the error wrapped in several pages worth of html, starting like;
$ curl -s http://localhost:5000/
<!DOCTYPE html>
<html>
<head>
<title>NameError: name 'XXmsg' is not defined
// Werkzeug Debugger</title>
<link rel="stylesheet" href="?__debugger__=yes&cmd=resource&f=style.css">
<link rel="shortcut icon"
href="?__debugger__=yes&cmd=resource&f=console.png">
<script src="?__debugger__=yes&cmd=resource&f=debugger.js"></script>
<script>
var CONSOLE_MODE = false,
EVALEX = true,
EVALEX_TRUSTED = false,
SECRET = "Mq5TSy6QE4OuOHUfvk8b";
</script>
</head>
<body style="background-color: #fff">
<div class="debugger">
Emitting html makes sense if you're creating a page load app. But I'm creating an api that only returns json.
Is there anyway to prevent Flask from emitting html at all?
Thanks
Mike
Have a look at the section Returning API Errors as JSON of the Flask docs.
Basically, you have to replace the default error handler with a function that returns the error as json. A very basic example:
#app.errorhandler(HTTPException)
def handle_exception(exception):
response = exception.get_response()
response.content_type = "application/json"
response.data = json.dumps({"code": exception.code})
return response
The accepted response gives a good hint for handling HTTPException but it won't work for all exceptions unless you create a handler for the mother of all exceptions:Exception. And you might not want to do this for security reasons, if you have some custom defined exceptions with sensible data it'll get handled by this handler.
I suspect the true reason you have those lengthy html responses is because you started your flask app with the --debug option.
from flask import Flask, jsonify, request
from flask_restful import Resource, Api
app = Flask(__name__)
api = Api(app)
class display(Resource):
def get(self):
return jsonify({"message":"hello world"})
def post(self):
data = request.get_json() # status code
return jsonify({'data': data}), 201
class square(Resource):
def get(self, num):
return jsonify({'square': num**2})
api.add_resource(display, '/display')
api.add_resource(square, '/square/<int:num>')
driver function
if __name__ == '__main__':
app.run(port = '5008')
This is the html file created using angularjs.This sends http request to the url given and receives data and displays it on th html page.
<!DOCTYPE html>
<html>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.9/angular.min.js"></script>
<body>
<div ng-app="myApp" ng-controller="myCtrl">
<p>Today's welcome message is:</p>
<h1>{{message}}</h1>
</div>
<p>The $http service requests a page on the server, and the response is set as the value of "myWelcome" variable.</p>
<script>
var app = angular.module('app', []);
app.controller("myCtrl", function($http) {
var app = this;
$http.get("http://127.0.0.1:5008/display")
.success(function(data) {
app.message = data;
})
});
</script>
</body>
</html>
I am getting this error while running the code:
Today's welcome message is:
{{message}}
The $http service requests a page on the server, and the response is set as the value of "myWelcome" variable.
I am trying to access an external url https://data.dublinked.ie/cgi-bin/rtpi/realtimebusinformation?stopid=184&format=json in a Flask view
I get the error,
Not Found
The requested URL was not found on the server. If you entered the URL
manually please check your spelling and try again.
Is that my local server that flask is looking for this url on. And if so why? I am running flask locally.
The view, services.py
from flask import Flask, Response
import json
import urllib2
app = Flask(__name__)
#app.route('/')
def test():
return 'Everything is running!'
#app.route('/stopid')
def stopid():
dublin_bus_url = "https://data.dublinked.ie/cgi-bin/rtpi/realtimebusinformation?stopid=184&format=json"
response = urllib2.urlopen(dublin_bus_url)
json_response = json.load(response)
routes = set()
for result in json_response["results"]:
routes.add(result["route"])
return json.dumps(list(routes))
if __name__ == '__main__':
app.run()
The index.html and script is,
<!DOCTYPE html>
<html>
<head>
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
</head>
<body>
<script>
d3.json("/stopid", function(error, routes) {
routes.forEach(function(route) {
console.log(route)
});
});
</script>
</body>
</html>
I am new to Flask but this must not be the way to deal with an external link in a view.
The code above is adopted from this excellent tutorial for the Donorschoose api.
https://youtu.be/bzl4hCH2CdY
https://github.com/VidyaSource/starting-with-data
Thanks,
If we assume that the HTML file is not being served by flask:
You need to enable Cross origin resource sharing. You can do this by creating a response and setting it's header Access-Control-Allow-Origin to *: that is everyone. Or you can set it to your own domain when deploying.
resp.headers['Access-Control-Allow-Origin'] = '*'
Also, you're calling d3.json("/stopid" ... you need to change this to:
d3.json("http://localhost:5000/stopid" ...
Complete code:
from flask import Flask, Response, jsonify
import json
import urllib2
app = Flask(__name__)
#app.route('/')
def test():
return 'Everything is running!'
#app.route('/stopid')
def stopid():
dublin_bus_url = "https://data.dublinked.ie/cgi-bin/rtpi/realtimebusinformation?stopid=184&format=json"
my_response = urllib2.urlopen(dublin_bus_url)
json_response = json.load(my_response)
routes = set()
for result in json_response["results"]:
routes.add(result["route"])
resp = jsonify(list(routes))
resp.headers['Access-Control-Allow-Origin'] = '*'
return resp
if __name__ == '__main__':
app.run()
If the HTML is being served by flask, there is no need to enable cross origin sharing.
#app.route('/d3')
def d3():
return render_template('d3.html')
Call the link to this url using:
d3.json("{{ url_for('stopid') }}", ...
But this isn't exactly reliable, because you don't want to use the api using javascript when you can do it in flask itself.
I try to create a little website using web.py and webpysocketio, and I have a problem: Web.py doesn't seem to find any file besides the index.html.
Here's my webpy app:
import web
from socketio import SocketIOServer
from gevent import monkey
monkey.patch_all()
from webpy_socketio import *
urls = (
'/', 'index',
)
urls += socketio_urls
app = web.application(urls, globals())
SOCKETIO_HOST = ""
SOCKETIO_PORT = 8080
application = app.wsgifunc()
if __name__ == "__main__":
SocketIOServer((SOCKETIO_HOST, SOCKETIO_PORT), application, resource="socket.io").serve_forever()
class index:
def GET(self):
render = web.template.render('templates/')
return render.index()
#on_message(channel="my channel")
def message(request, socket, context, message):
socket.send_and_broadcast_channel(message)
In my template folder I have the index.html (and the socketio_scripts.html):
<html>
<head>
<meta charset="utf-8">
<title>webpysocketio TEST</title>
<object type="text/html" data="socketio_scripts.html">
<script>
var socket = new io.Socket();
socket.connect();
socket.on('connect', function() {
socket.subscribe('my channel');
socket.send('asdf');
});
</script>
</object>
</head>
<body>
<div>
</div>
</body>
</html>
Now when I run the webiste, after I visit it in a browser, I get the following on my terminal:
IP - - [DATE] "GET /socketio_scripts.html HTTP/1.1" 404 135 0.004273
Why does it not find the other html file?
I suspect that /socketio_scripts.html is not the correct url for that file - it seems odd that that file would be at the root of your document tree.
According to several pages, it should be in .../templates/. I also doubt very much it would ever be accessed by GET
Have a look at the web.py tutorial. One big difference with your code is that all the pages have to be listed in the urls dispatch table:
urls = (
'/', 'hello',
'/bye', 'bye')
The static files which you dont want to be served from GET function have to be placed in a static file , in your example the "socketio_scripts.html"
and accessed by the full url "http://localhost/static/socketio_scripts.html". http://webpy.org/cookbook/staticfiles
I can't seem to figure out how to using Flask's streaming. Here's my code:
#app.route('/scans/')
def scans_query():
url_for('static', filename='.*')
def generate():
yield render_template('scans.html')
for i in xrange(50):
sleep(.5)
yield render_template('scans.html', **locals())
return Response(stream_with_context(generate()))
and in my template:
<p>{% i %}</p>
I would like to see a counter on the page that changes every half second. Instead, the closest I've gotten is the page printing out each number on the next line.
To replace existing content on the page you might need javascript i.e., you could send it or make it to make requests for you, use long polling, websockets, etc. There are many ways to do it, here's one that uses server send events:
#!/usr/bin/env python
import itertools
import time
from flask import Flask, Response, redirect, request, url_for
app = Flask(__name__)
#app.route('/')
def index():
if request.headers.get('accept') == 'text/event-stream':
def events():
for i, c in enumerate(itertools.cycle('\|/-')):
yield "data: %s %d\n\n" % (c, i)
time.sleep(.1) # an artificial delay
return Response(events(), content_type='text/event-stream')
return redirect(url_for('static', filename='index.html'))
if __name__ == "__main__":
app.run(host='localhost', port=23423)
Where static/index.html:
<!doctype html>
<title>Server Send Events Demo</title>
<style>
#data {
text-align: center;
}
</style>
<script src="http://code.jquery.com/jquery-latest.js"></script>
<script>
if (!!window.EventSource) {
var source = new EventSource('/');
source.onmessage = function(e) {
$("#data").text(e.data);
}
}
</script>
<div id="data">nothing received yet</div>
The browser reconnects by default in 3 seconds if the connection is lost. if there is nothing more to send the server could return 404 or just send some other than 'text/event-stream' content type in response to the next request. To stop on the client side even if the server has more data you could call source.close().
Note: if the stream is not meant to be infinite then use other techniques (not SSE) e.g., send javascript snippets to replace the text (infinite <iframe> technique):
#!/usr/bin/env python
import time
from flask import Flask, Response
app = Flask(__name__)
#app.route('/')
def index():
def g():
yield """<!doctype html>
<title>Send javascript snippets demo</title>
<style>
#data {
text-align: center;
}
</style>
<script src="http://code.jquery.com/jquery-latest.js"></script>
<div id="data">nothing received yet</div>
"""
for i, c in enumerate("hello"):
yield """
<script>
$("#data").text("{i} {c}")
</script>
""".format(i=i, c=c)
time.sleep(1) # an artificial delay
return Response(g())
if __name__ == "__main__":
app.run(host='localhost', port=23423)
I've inlined the html here to show that there is nothing more to it (no magic). Here's the same as above but using templates:
#!/usr/bin/env python
import time
from flask import Flask, Response
app = Flask(__name__)
def stream_template(template_name, **context):
# http://flask.pocoo.org/docs/patterns/streaming/#streaming-from-templates
app.update_template_context(context)
t = app.jinja_env.get_template(template_name)
rv = t.stream(context)
# uncomment if you don't need immediate reaction
##rv.enable_buffering(5)
return rv
#app.route('/')
def index():
def g():
for i, c in enumerate("hello"*10):
time.sleep(.1) # an artificial delay
yield i, c
return Response(stream_template('index.html', data=g()))
if __name__ == "__main__":
app.run(host='localhost', port=23423)
Where templates/index.html:
<!doctype html>
<title>Send javascript with template demo</title>
<style>
#data {
text-align: center;
}
</style>
<script src="http://code.jquery.com/jquery-latest.js"></script>
<div id="data">nothing received yet</div>
{% for i, c in data: %}
<script>
$("#data").text("{{ i }} {{ c }}")
</script>
{% endfor %}
I think if you're going to use templates like that, you might need to use the stream_template function given here: http://flask.pocoo.org/docs/patterns/streaming/#streaming-from-templates
I didn't test this, but it might look like:
def stream_template(template_name, **context):
app.update_template_context(context)
t = app.jinja_env.get_template(template_name)
rv = t.stream(context)
rv.enable_buffering(5)
return rv
#app.route('/scans/')
def scans_query():
url_for('static', filename='.*')
def generate():
for i in xrange(50):
sleep(.5)
yield i
return Response(stream_template('scans.html', i=generate()))