Skip to content

Commit 9d8b58a

Browse files
committed
Example app - Lots of improvements needed :)
0 parents  commit 9d8b58a

File tree

9 files changed

+370
-0
lines changed

9 files changed

+370
-0
lines changed

Procfile

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
web: gunicorn app:app --log-file=-

app-reddit.py

+96
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
#!/usr/bin/python
2+
# -*- coding: utf-8 -*-
3+
# Copyright 2014 J. Fernando Sánchez Rada - Grupo de Sistemas Inteligentes
4+
# DIT, UPM
5+
#
6+
# Licensed under the Apache License, Version 2.0 (the "License");
7+
# you may not use this file except in compliance with the License.
8+
# You may obtain a copy of the License at
9+
#
10+
# http://www.apache.org/licenses/LICENSE-2.0
11+
#
12+
# Unless required by applicable law or agreed to in writing, software
13+
# distributed under the License is distributed on an "AS IS" BASIS,
14+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
# See the License for the specific language governing permissions and
16+
# limitations under the License.
17+
'''
18+
Example flask application that uses the EUROSENTIMENT Sentiment Analysis
19+
services to analyse posts from reddit.
20+
'''
21+
from flask import Flask, abort, request
22+
from uuid import uuid4
23+
import requests
24+
import requests.auth
25+
import urllib
26+
import config
27+
28+
app = Flask(__name__)
29+
app.debug = True
30+
@app.route('/')
31+
def homepage():
32+
text = '<a href="%s">Authenticate with reddit</a>'
33+
return text % make_authorization_url()
34+
35+
36+
def make_authorization_url():
37+
# Generate a random string for the state parameter
38+
# Save it for use later to prevent xsrf attacks
39+
state = str(uuid4())
40+
save_created_state(state)
41+
params = {"client_id": config.CLIENT_ID,
42+
"response_type": "code",
43+
"state": state,
44+
"redirect_uri": config.REDIRECT_URI,
45+
"duration": "temporary",
46+
"scope": "identity"}
47+
url = "https://ssl.reddit.com/api/v1/authorize?" + urllib.urlencode(params)
48+
return url
49+
50+
51+
# Left as an exercise to the reader.
52+
# You may want to store valid states in a database or memcache.
53+
def save_created_state(state):
54+
pass
55+
def is_valid_state(state):
56+
return True
57+
58+
@app.route('/reddit_callback')
59+
def reddit_callback():
60+
error = request.args.get('error', '')
61+
if error:
62+
return "Error: " + error
63+
state = request.args.get('state', '')
64+
if not is_valid_state(state):
65+
# Uh-oh, this request wasn't started by us!
66+
abort(403)
67+
code = request.args.get('code')
68+
access_token = get_token(code)
69+
return "Your reddit username is: %s" % get_username(access_token)
70+
71+
def get_token(code):
72+
client_auth = requests.auth.HTTPBasicAuth(config.CLIENT_ID,
73+
config.CLIENT_SECRET)
74+
post_data = {"grant_type": "authorization_code",
75+
"code": code,
76+
"redirect_uri": config.REDIRECT_URI}
77+
headers = {"User-agent": "/u/balkian's first reddit app"}
78+
response = requests.post("https://ssl.reddit.com/api/v1/access_token",
79+
auth=client_auth,
80+
headers=headers,
81+
data=post_data)
82+
token_json = response.json()
83+
print("Got token json")
84+
print(token_json)
85+
return token_json["access_token"]
86+
87+
88+
def get_username(access_token):
89+
headers = {"Authorization": "bearer " + access_token}
90+
response = requests.get("https://oauth.reddit.com/api/v1/me", headers=headers)
91+
me_json = response.json()
92+
return me_json['name']
93+
94+
95+
if __name__ == '__main__':
96+
app.run(debug=True, port=5000)

app.py

+86
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
#!/usr/bin/python
2+
# -*- coding: utf-8 -*-
3+
# Copyright 2014 J. Fernando Sánchez Rada - Grupo de Sistemas Inteligentes
4+
# DIT, UPM
5+
#
6+
# Licensed under the Apache License, Version 2.0 (the "License");
7+
# you may not use this file except in compliance with the License.
8+
# You may obtain a copy of the License at
9+
#
10+
# http://www.apache.org/licenses/LICENSE-2.0
11+
#
12+
# Unless required by applicable law or agreed to in writing, software
13+
# distributed under the License is distributed on an "AS IS" BASIS,
14+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
# See the License for the specific language governing permissions and
16+
# limitations under the License.
17+
'''
18+
Example flask application that uses the EUROSENTIMENT Sentiment Analysis
19+
services to analyse posts from reddit.
20+
'''
21+
from flask import Flask, render_template, request
22+
import clients
23+
import config
24+
import json
25+
import praw
26+
from clients import ServiceClient
27+
28+
app = Flask(__name__)
29+
user_agent = "Test of the EUROSENTIMENT services"
30+
31+
class LazyEncoder(json.JSONEncoder):
32+
def default(self, obj):
33+
try:
34+
return json.JSONEncoder.default(self, obj)
35+
except TypeError:
36+
try:
37+
return json.JSONEncoder.default(self, vars(obj))
38+
except TypeError:
39+
return "**not serializable**"
40+
41+
@app.route('/')
42+
@app.route('/r/<subreddit>')
43+
def home(subreddit="python"):
44+
r = praw.Reddit(user_agent=user_agent)
45+
subreddit = r.get_subreddit(subreddit)
46+
submissions = subreddit.get_top(limit=10)
47+
return render_template("posts.html", submissions=submissions)
48+
49+
@app.route('/submission/<submission_id>')
50+
def submission(submission_id):
51+
r = praw.Reddit(user_agent=user_agent)
52+
s = ServiceClient(config.TOKEN, config.ENDPOINT)
53+
submission = r.get_submission(submission_id=submission_id,
54+
comment_sort="top")
55+
comments = praw.helpers.flatten_tree(submission.comments)
56+
comments = sorted(comments, reverse=True, key=lambda x: x.score)[0:20]
57+
for comment in comments:
58+
res = s.request(input=comment.body,
59+
intype="direct",
60+
informat="text",
61+
outformat="json-ld")
62+
print res
63+
comment.results = res
64+
comment.polarity = 0
65+
num = 0
66+
for entry in res["entries"]:
67+
for opinion in entry["opinions"]:
68+
print "Polarity: %s" % comment.polarity
69+
num+=1
70+
comment.polarity+=opinion["marl:polarityValue"]
71+
comment.polarity = comment.polarity / num
72+
return render_template('comments.html', comments=comments)
73+
74+
75+
#return render_template("comments.html", comments=comments)
76+
77+
@app.route('/subreddit')
78+
def subreddit():
79+
r = praw.Reddit(user_agent=user_agent)
80+
subreddit = r.get_subreddit('learnpython')
81+
submissions = subreddit.get_top(limit=10)
82+
return json.dumps([vars(sub) for sub in submissions], cls=LazyEncoder)
83+
84+
if __name__ == '__main__':
85+
app.debug = True
86+
app.run()

clients.py

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
#!/usr/bin/python
2+
# -*- coding: utf-8 -*-
3+
# Copyright 2014 J. Fernando Sánchez Rada - Grupo de Sistemas Inteligentes
4+
# DIT, UPM
5+
#
6+
# Licensed under the Apache License, Version 2.0 (the "License");
7+
# you may not use this file except in compliance with the License.
8+
# You may obtain a copy of the License at
9+
#
10+
# http://www.apache.org/licenses/LICENSE-2.0
11+
#
12+
# Unless required by applicable law or agreed to in writing, software
13+
# distributed under the License is distributed on an "AS IS" BASIS,
14+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
# See the License for the specific language governing permissions and
16+
# limitations under the License.
17+
'''
18+
Wrappers around the EUROSENTIMENT APIs.
19+
'''
20+
21+
import json
22+
import requests
23+
24+
class ServiceClient():
25+
26+
def __init__(self, token, endpoint):
27+
self.endpoint = endpoint
28+
self.token = token
29+
30+
def request(self, **kwargs):
31+
headers = {'x-eurosentiment-token': self.token}
32+
response = requests.post(self.endpoint,
33+
data=kwargs,
34+
headers=headers)
35+
return json.loads(response.content)

config.py

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import os
2+
3+
TOKEN = os.environ.get("TOKEN", "")
4+
ENDPOINT = os.environ.get("ENDPOINT", "http://eurosentiment-endpoint.herokuapp.com")

requirements.txt

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Flask==0.10.1
2+
gunicorn==19.0.0
3+
praw==2.1.17
4+
requests==2.3.0

static/css/main.css

+85
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
#vis text:hover { margin: 0;opacity: .7 !important ; }
2+
#vis {
3+
margin: 0;
4+
}
5+
#header {
6+
position: relative;
7+
min-width: 1100px;
8+
width: 100%;
9+
padding: 0.5em;
10+
color: white;
11+
background-color: black;
12+
font-family: 'Architects Daughter', cursive;
13+
}
14+
#left-bar {
15+
float: left;
16+
}
17+
.wrapper {
18+
position: relative;
19+
width: 1100px;
20+
margin: 0 auto;
21+
}
22+
.newspaper-a
23+
{
24+
font-family: "Lucida Sans Unicode", "Lucida Grande", Sans-Serif;
25+
font-size: 12px;
26+
margin: 10px;
27+
width: 480px;
28+
text-align: left;
29+
border-collapse: collapse;
30+
border: 1px solid #69c;
31+
}
32+
.newspaper-a th
33+
{
34+
padding: 12px 17px 12px 17px;
35+
font-weight: normal;
36+
font-size: 14px;
37+
color: #039;
38+
border-bottom: 1px dashed #69c;
39+
}
40+
.newspaper-a td
41+
{
42+
padding: 7px 17px 7px 17px;
43+
color: #669;
44+
}
45+
.newspaper-a tbody tr:hover td
46+
{
47+
color: #339;
48+
background: #d0dafd;
49+
}
50+
51+
.modal {
52+
display: none;
53+
position: fixed;
54+
z-index: 1000;
55+
top: 0;
56+
left: 0;
57+
height: 100%;
58+
width: 100%;
59+
background: rgba( 255, 255, 255, .8 )
60+
url('/static/img/loading.gif')
61+
50% 50%
62+
no-repeat;
63+
}
64+
65+
/* When the body has the loading class, we turn
66+
the scrollbar off with overflow:hidden */
67+
body.loading {
68+
overflow: hidden;
69+
}
70+
71+
/* Anytime the body has the loading class, our
72+
modal element will be visible */
73+
body.loading .modal {
74+
display: block;
75+
}
76+
77+
html, body {
78+
position: relative;
79+
width: 100%;
80+
margin: 0;
81+
}
82+
* {
83+
margin:0px;
84+
padding:0px;
85+
}

templates/comments.html

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
5+
<title>Posts in reddit</title>
6+
</head>
7+
<body>
8+
<div id="header">
9+
<div class="wrapper"> Simple demo of the service</div>
10+
</div>
11+
<div class="wrapper">
12+
<table class="newspaper-a">
13+
<thead>
14+
<tr><th>Polarity</th><th>Comment</th><th>Score</th></tr>
15+
</thead>
16+
<tbody>
17+
{% for comment in comments %}
18+
<tr>
19+
<td>{{ comment.polarity }}</td>
20+
<td>{{ comment.body }}</td>
21+
<td>{{ comment.score }}</td>
22+
</tr>
23+
{% endfor %}
24+
</tbody>
25+
</table>
26+
</div>
27+
<link href='http://fonts.googleapis.com/css?family=Architects+Daughter' rel='stylesheet' type='text/css'>
28+
<link rel="stylesheet" href="{{ url_for('static', filename='css/main.css')}}"></style>
29+
</body>
30+
</html>

templates/posts.html

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
5+
<title>Posts in reddit</title>
6+
</head>
7+
<body>
8+
<div id="header">
9+
<div class="wrapper"> Simple demo of the service</div>
10+
</div>
11+
<div class="wrapper">
12+
<table class="newspaper-a">
13+
<thead>
14+
<tr><th>Submission</th><th>Comments</th></tr>
15+
</thead>
16+
<tbody>
17+
{% for submission in submissions %}
18+
<tr>
19+
<td><a href="{{ url_for("submission", submission_id=submission.id) }}">{{ submission.title }}</a></td>
20+
<td>{{ submission.num_comments }}</td>
21+
</tr>
22+
{% endfor %}
23+
</tbody>
24+
</table>
25+
</div>
26+
<link href='http://fonts.googleapis.com/css?family=Architects+Daughter' rel='stylesheet' type='text/css'>
27+
<link rel="stylesheet" href="{{ url_for('static', filename='css/main.css')}}"></style>
28+
</body>
29+
</html>

0 commit comments

Comments
 (0)