From 80ef8c19aa5f9c43c91e4e4afaa0eac5127e2611 Mon Sep 17 00:00:00 2001 From: Philipp Moritz Date: Wed, 20 Mar 2019 18:47:12 -0700 Subject: [PATCH] Add initial news reader example (#4348) --- doc/source/example-newsreader.rst | 29 +++++++++++ doc/source/index.rst | 1 + examples/newsreader/server.py | 85 +++++++++++++++++++++++++++++++ 3 files changed, 115 insertions(+) create mode 100644 doc/source/example-newsreader.rst create mode 100644 examples/newsreader/server.py diff --git a/doc/source/example-newsreader.rst b/doc/source/example-newsreader.rst new file mode 100644 index 000000000..1b87175e1 --- /dev/null +++ b/doc/source/example-newsreader.rst @@ -0,0 +1,29 @@ +News Reader +=========== + +This document shows how to implement a simple news reader using Ray. The reader +consists of a simple Vue.js `frontend`_ and a backend consisting of a Flask +server and a Ray actor. View the `code for this example`_. + +To run this example, you will need to install NPM and a few python dependencies. + +.. code-block:: bash + + pip install atoma + pip install flask + + +To use this example you need to + +* In the ``ray/examples/newsreader`` directory, start the server with + ``python server.py``. +* Clone the client code with ``git clone https://github.com/ray-project/qreader`` +* Start the client with ``cd qreader; npm install; npm run dev`` +* You can now add a channel by clicking "Add channel" and for example pasting + ``http://news.ycombinator.com/rss`` into the field. +* Star some of the articles and dump the database by running + ``sqlite3 newsreader.db`` in a terminal in the ``ray/examples/newsreader`` + directory and entering ``SELECT * FROM news;``. + +.. _`frontend`: https://github.com/saqueib/qreader +.. _`code for this example`: https://github.com/ray-project/ray/tree/master/examples/newsreader diff --git a/doc/source/index.rst b/doc/source/index.rst index d29f34f4e..b04009cf8 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -115,6 +115,7 @@ Ray comes with libraries that accelerate deep learning and reinforcement learnin example-rl-pong.rst example-policy-gradient.rst example-parameter-server.rst + example-newsreader.rst example-resnet.rst example-a3c.rst example-lbfgs.rst diff --git a/examples/newsreader/server.py b/examples/newsreader/server.py new file mode 100644 index 000000000..e30f8269c --- /dev/null +++ b/examples/newsreader/server.py @@ -0,0 +1,85 @@ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import atoma +from flask import Flask, jsonify, request +from flask_cors import CORS +import requests +import sqlite3 + +import ray + + +@ray.remote +class NewsServer(object): + + def __init__(self): + self.conn = sqlite3.connect("newsreader.db") + c = self.conn.cursor() + c.execute("""CREATE TABLE IF NOT EXISTS news + (title text, link text, + description text, published timestamp, + feed url, liked bool)""") + self.conn.commit() + + def retrieve_feed(self, url): + response = requests.get(url) + feed = atoma.parse_rss_bytes(response.content) + items = [] + c = self.conn.cursor() + for item in feed.items: + items.append({"title": item.title, + "link": item.link, + "description": item.description, + "description_text": item.description, + "pubDate": str(item.pub_date)}) + c.execute("""INSERT INTO news (title, link, description, + published, feed, liked) values + (?, ?, ?, ?, ?, ?)""", ( + item.title, item.link, item.description, + item.pub_date, feed.link, False)) + self.conn.commit() + + return {"channel": {"title": feed.title, + "link": feed.link, + "url": feed.link}, + "items": items} + + def like_item(self, url, is_faved): + c = self.conn.cursor() + if is_faved: + c.execute("UPDATE news SET liked = 1 WHERE link = ?", (url,)) + else: + c.execute("UPDATE news SET liked = 0 WHERE link = ?", (url,)) + self.conn.commit() + + +# instantiate the app +app = Flask(__name__) +app.config.from_object(__name__) + +# enable CORS +CORS(app) + + +@app.route("/api", methods=["POST"]) +def dispatcher(): + req = request.get_json() + method_name = req["method_name"] + method_args = req["method_args"] + if hasattr(dispatcher.server, method_name): + method = getattr(dispatcher.server, method_name) + # Doing a blocking ray.get right after submitting the task + # might be bad for performance if the task is expensive. + result = ray.get(method.remote(*method_args)) + return jsonify(result) + else: + return jsonify( + {"error": "method_name '" + method_name + "' not found"}) + + +if __name__ == "__main__": + ray.init(num_cpus=2) + dispatcher.server = NewsServer.remote() + app.run()