Skip to content

Commit

Permalink
Merge pull request #7 from ibm-watson-data-lab/david-gateway-work
Browse files Browse the repository at this point in the history
David gateway work
  • Loading branch information
David Taieb authored Nov 9, 2017
2 parents fa80a18 + 66fe7b8 commit e441807
Show file tree
Hide file tree
Showing 3 changed files with 184 additions and 22 deletions.
184 changes: 173 additions & 11 deletions pixiegateway/chartsManager.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,48 @@
# See the License for the specific language governing permissions and
# limitations under the License.
# -------------------------------------------------------------------------------
from pixiedust.utils.storage import Storage
import uuid
import base64
import os
from abc import ABCMeta, abstractmethod
import requests
import time
import datetime
from six import with_metaclass, iteritems
from pixiedust.utils.storage import Storage
from traitlets.config.configurable import SingletonConfigurable
from traitlets import Unicode, default, Integer
from tornado.util import import_object
from tornado.log import app_log
from .pixieGatewayApp import PixieGatewayApp

class ChartStorage(Storage):
class ChartStorage(with_metaclass(ABCMeta)):
"""
Interface to the Chart Storage
"""
@abstractmethod
def store_chart(self, payload):
"returns chart model"
pass
@abstractmethod
def get_chart(self, chart_id):
"returns chart model"
pass
@abstractmethod
def delete_chart(self, chart_id):
pass
@abstractmethod
def list_charts(self):
pass
@abstractmethod
def get_charts(self, page_num=0, page_size=10):
pass

class SQLLiteChartStorage(ChartStorage, Storage):
"Chart storage class for SQLLite PixieDust DB (default)"
CHARTS_TBL_NAME="CHARTS"
def __init__(self):
self._initTable( ChartStorage.CHARTS_TBL_NAME,
self._initTable( SQLLiteChartStorage.CHARTS_TBL_NAME,
'''
CHARTID TEXT NOT NULL PRIMARY KEY,
AUTHOR TEXT NOT NULL,
Expand All @@ -31,11 +66,10 @@ def __init__(self):

def store_chart(self, payload):
chart_id = str(uuid.uuid4())
print(chart_id.__class__)
self.insert("""
INSERT INTO {0} (CHARTID,AUTHOR,DATE,DESCRIPTION,CONTENT,RENDERERID)
VALUES (?,?,CURRENT_TIMESTAMP,?,?,?)
""".format(ChartStorage.CHARTS_TBL_NAME), (
""".format(SQLLiteChartStorage.CHARTS_TBL_NAME), (
chart_id,
"username",
payload.get("description", ""),
Expand All @@ -48,14 +82,14 @@ def store_chart(self, payload):
def get_chart(self, chart_id):
return self.fetchOne(
"""SELECT * from {0} WHERE CHARTID='{1}'""".format(
ChartStorage.CHARTS_TBL_NAME, chart_id
SQLLiteChartStorage.CHARTS_TBL_NAME, chart_id
)
)

def delete_chart(self, chart_id):
rows_deleted = self.delete(
"""DELETE FROM {0} WHERE CHARTID='{1}'""".format(
ChartStorage.CHARTS_TBL_NAME, chart_id
SQLLiteChartStorage.CHARTS_TBL_NAME, chart_id
)
)
print("Row Deleted: {}".format(rows_deleted))
Expand All @@ -66,7 +100,7 @@ def walker(row):
print(row['CHARTID'])
self.execute("""
SELECT * FROM {0}
""".format(ChartStorage.CHARTS_TBL_NAME),
""".format(SQLLiteChartStorage.CHARTS_TBL_NAME),
walker
)

Expand All @@ -75,11 +109,139 @@ def get_charts(self, page_num=0, page_size=10):
offset = limit * max(0, page_num)
charts_list = self.fetchMany("""
SELECT CHARTID,AUTHOR,DATE,DESCRIPTION,RENDERERID FROM {0} LIMIT {1} OFFSET {2}
""".format(ChartStorage.CHARTS_TBL_NAME, str(limit), str(offset))
""".format(SQLLiteChartStorage.CHARTS_TBL_NAME, str(limit), str(offset))
)

total_count = self.fetchOne('SELECT COUNT(CHARTID) as count from {0}'.format(ChartStorage.CHARTS_TBL_NAME))['count']
total_count = self.fetchOne('SELECT COUNT(CHARTID) as count from {0}'.format(SQLLiteChartStorage.CHARTS_TBL_NAME))['count']

return {"page_num": page_num, "page_size": page_size, "total_count": total_count, "charts_list": charts_list}

chart_storage = ChartStorage()
class CloudantChartStorage(ChartStorage):
CHART_DB_NAME = "pixiegateway_chart"
class CloudantConfig(SingletonConfigurable):
def __init__(self, **kwargs):
kwargs['parent'] = PixieGatewayApp.instance()
super(CloudantChartStorage.CloudantConfig, self).__init__(**kwargs)

host = Unicode(None, config=True, help="Cloudant Chart Storage hostname")
protocol = Unicode("https", config=True, help="Cloudant Chart Storage protocol")
port = Integer(443, config=True, help="Cloudant Chart Storage port")
username = Unicode(None, config=True, help="Cloudant Chart Storage username")
password = Unicode(None, config=True, help="Cloudant Chart Storage password")

@default('host')
def host_default(self):
return os.getenv("PG_CLOUDANT_HOST", "")

@default('protocol')
def protocol_default(self):
return os.getenv("PG_CLOUDANT_PROTOCOL", "https")

@default('port')
def port_default(self):
return int(os.getenv("PG_CLOUDANT_PORT", 443))

@default('username')
def username_default(self):
return os.getenv("PG_CLOUDANT_USERNAME", "")

@default('password')
def password_default(self):
return os.getenv("PG_CLOUDANT_PASSWORD", "")

def __init__(self):
self.host = CloudantChartStorage.CloudantConfig.instance().host
self.protocol = CloudantChartStorage.CloudantConfig.instance().protocol
self.port = CloudantChartStorage.CloudantConfig.instance().port
self.username = CloudantChartStorage.CloudantConfig.instance().username
self.password = CloudantChartStorage.CloudantConfig.instance().password

#make sure the database exists, if not create it
response = requests.head(self.build_url(), headers=self.get_headers())
if response.status_code == 404:
response = requests.put( self.build_url(), headers=self.get_headers())

if response.status_code != 200 and response.status_code != 201:
app_log.error("Unexcepted error while connecting to cloudant: %s", response.text)
else:
app_log.info("Succesfully connected to Cloudant db: %s-%s", self.host, CloudantChartStorage.CHART_DB_NAME)

def get_headers(self):
return {
'Authorization': 'Basic {}'.format(base64.b64encode('{}:{}'.format(self.username, self.password).encode()).decode()),
'Accept': 'application/json'
}

def build_url(self, path="", **kwargs):
url = "{}://{}:{}/{}/{}".format(self.protocol, self.host, self.port, CloudantChartStorage.CHART_DB_NAME, path)
return url if len(kwargs)==0 else "{}?{}".format(url,"&".join(["{}={}".format(k,v) for k,v in iteritems(kwargs)]))

def store_chart(self, payload):

chart_id = str(uuid.uuid4())
payload = {
'_id': chart_id,
'CHARTID': chart_id,
'DATE': time.time(),
'AUTHOR': "username",
'DESCRIPTION': payload.get('description', ''),
'CONTENT': payload['chart'],
'RENDERERID': payload.get("rendererId", "")
}

response = requests.post(self.build_url(), json = payload, headers=self.get_headers())
if not response.ok:
raise Exception(response.text)
#return the chart_model for this newly stored chart
return payload

def format_chart_model(self, chart_model):
chart_model['DATE'] = datetime.datetime.fromtimestamp(chart_model['DATE']).strftime("%Y-%m-%d %H:%M:%S")
return chart_model

def get_chart(self, chart_id):
return self.format_chart_model(requests.get(self.build_url(chart_id), headers=self.get_headers()).json())

def delete_chart(self, chart_id):
response = requests.delete(self.build_url(chart_id), headers = self.get_headers())
if not response.ok:
raise Exception(response.text)

def list_charts(self):
return self.get_charts()

def get_charts(self, page_num=0, page_size=10):
limit = max(1, page_size)
offset = limit * max(0, page_num)
url = self.build_url("_all_docs", limit=limit, skip=offset, include_docs="true")
payload = requests.get(url, headers=self.get_headers()).json()
return {
"page_num": page_num,
"page_size": page_size,
"total_count": payload['total_rows'],
"charts_list": [self.format_chart_model(row['doc']) for row in payload['rows']]
}

class SingletonChartStorage(SingletonConfigurable):
"""
Singleton use to access concrete instance of chart storage
"""

chart_storage_class = Unicode(None, config=True, help="Chart storage class")

@default('chart_storage_class')
def chart_storage_class_default(self):
return os.getenv('PG_CHART_STORAGE', 'pixiegateway.chartsManager.SQLLiteChartStorage')

def __init__(self, **kwargs):
kwargs['parent'] = PixieGatewayApp.instance()
super(SingletonChartStorage, self).__init__(**kwargs)

self.chart_storage = import_object(self.chart_storage_class)()

def __getattr__(self, name):
if name == "chart_storage":
raise AttributeError("{0} attribute not found".format(name))
if self.chart_storage is not None and hasattr(self.chart_storage, name):
return getattr(self.chart_storage, name)
raise AttributeError("{0} attribute not found".format(name))
10 changes: 5 additions & 5 deletions pixiegateway/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
from .session import SessionManager
from .notebookMgr import NotebookMgr
from .managedClient import ManagedClientPool
from .chartsManager import chart_storage
from .chartsManager import SingletonChartStorage
from .utils import sanitize_traceback

class BaseHandler(tornado.web.RequestHandler):
Expand Down Expand Up @@ -175,7 +175,7 @@ class ChartShareHandler(BaseHandler):
def post(self, chart_id):
payload = json.loads(self.request.body.decode('utf-8'))
try:
chart_model = chart_storage.store_chart(payload)
chart_model = SingletonChartStorage.instance().store_chart(payload)
self.set_status(200)
self.write(json.dumps(chart_model))
self.finish()
Expand All @@ -184,7 +184,7 @@ def post(self, chart_id):
raise web.HTTPError(400, u'Share Chart error: {}'.format(exc))

def get(self, chart_id):
chart_model = chart_storage.get_chart(chart_id)
chart_model = SingletonChartStorage.instance().get_chart(chart_id)
if chart_model is not None:
self.render("template/showChart.html", chart_model=chart_model)
else:
Expand All @@ -193,7 +193,7 @@ def get(self, chart_id):

class ChartEmbedHandler(BaseHandler):
def get(self, chart_id, width, height):
chart_model = chart_storage.get_chart(chart_id)
chart_model = SingletonChartStorage.instance().get_chart(chart_id)
if chart_model is not None:
if 'RENDERERID' in chart_model:
content = chart_model['CONTENT']
Expand Down Expand Up @@ -221,7 +221,7 @@ def get(self, chart_id, width, height):

class ChartsHandler(BaseHandler):
def get(self, page_num=0, page_size=10):
self.write(chart_storage.get_charts(int(page_num), int(page_size)))
self.write(SingletonChartStorage.instance().get_charts(int(page_num), int(page_size)))

class StatsHandler(BaseHandler):
"""
Expand Down
12 changes: 6 additions & 6 deletions pixiegateway/tests/test_chartstorage.py

Large diffs are not rendered by default.

0 comments on commit e441807

Please sign in to comment.