mirror of
https://github.com/Tautulli/Tautulli.git
synced 2025-03-12 04:35:40 -07:00
511 lines
20 KiB
Python
511 lines
20 KiB
Python
#!/usr/bin/env python
|
|
# -*- coding: utf-8 -*-
|
|
|
|
# This file is part of PlexPy.
|
|
#
|
|
# PlexPy is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# PlexPy is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with PlexPy. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
import json
|
|
import hashlib
|
|
import os
|
|
import random
|
|
import re
|
|
import traceback
|
|
|
|
import cherrypy
|
|
import xmltodict
|
|
|
|
import plexpy
|
|
import database
|
|
import datafactory
|
|
import graphs
|
|
import logger
|
|
import plextv
|
|
import pmsconnect
|
|
import users
|
|
import versioncheck
|
|
|
|
|
|
cmd_list = ['getLogs', 'getVersion', 'checkGithub', 'shutdown',
|
|
'getSettings', 'restart', 'update', 'getApikey', 'getHistory',
|
|
'getMetadata', 'getUserips', 'getPlayby', 'getSync']
|
|
|
|
|
|
class Api(object):
|
|
def __init__(self, out='json'):
|
|
|
|
self.apikey = None
|
|
self.authenticated = False
|
|
self.cmd = None
|
|
self.kwargs = None
|
|
# For the responses
|
|
self.data = None
|
|
self.msg = None
|
|
self.result_type = 'error'
|
|
# Possible general params
|
|
self.callback = None
|
|
self.out_type = out
|
|
self.debug = None
|
|
|
|
def checkParams(self, *args, **kwargs):
|
|
|
|
if not plexpy.CONFIG.API_ENABLED:
|
|
self.msg = 'API not enabled'
|
|
elif not plexpy.CONFIG.API_KEY:
|
|
self.msg = 'API key not generated'
|
|
elif len(plexpy.CONFIG.API_KEY) != 32:
|
|
self.msg = 'API key not generated correctly'
|
|
elif 'apikey' not in kwargs:
|
|
self.msg = 'Parameter apikey is required'
|
|
elif kwargs.get('apikey', '') != plexpy.CONFIG.API_KEY:
|
|
self.msg = 'Invalid apikey'
|
|
elif 'cmd' not in kwargs:
|
|
self.msg = 'Parameter %s required. possible commands are: %s' % ', '.join(cmd_list)
|
|
elif 'cmd' in kwargs and kwargs.get('cmd') not in cmd_list:
|
|
self.msg = 'Unknown command, %s possible commands are: %s' % (kwargs.get('cmd', ''), ', '.join(cmd_list))
|
|
|
|
# Set default values or remove them from kwargs
|
|
|
|
self.callback = kwargs.pop('callback', None)
|
|
self.apikey = kwargs.pop('apikey', None)
|
|
self.cmd = kwargs.pop('cmd', None)
|
|
self.debug = kwargs.pop('debug', False)
|
|
# Allow override for the api.
|
|
self.out_type = kwargs.pop('out_type', 'json')
|
|
|
|
if self.apikey == plexpy.CONFIG.API_KEY and plexpy.CONFIG.API_ENABLED and self.cmd in cmd_list:
|
|
self.authenticated = True
|
|
self.msg = None
|
|
elif self.cmd == 'getApikey' and plexpy.CONFIG.API_ENABLED:
|
|
self.authenticated = True
|
|
# Remove the old error msg
|
|
self.msg = None
|
|
|
|
self.kwargs = kwargs
|
|
|
|
def _responds(self, result_type='success', data=None, msg=''):
|
|
|
|
if data is None:
|
|
data = {}
|
|
return {"response": {"result": result_type, "message": msg, "data": data}}
|
|
|
|
def _out_as(self, out):
|
|
|
|
if self.out_type == 'json':
|
|
cherrypy.response.headers['Content-Type'] = 'application/json;charset=UTF-8'
|
|
try:
|
|
out = json.dumps(out, indent=4, sort_keys=True)
|
|
if self.callback is not None:
|
|
cherrypy.response.headers['Content-Type'] = 'application/javascript'
|
|
# wrap with JSONP call if requested
|
|
out = self.callback + '(' + out + ');'
|
|
# if we fail to generate the output fake an error
|
|
except Exception as e:
|
|
logger.info(u"API :: " + traceback.format_exc())
|
|
out['message'] = traceback.format_exc()
|
|
out['result'] = 'error'
|
|
if self.out_type == 'xml':
|
|
cherrypy.response.headers['Content-Type'] = 'application/xml'
|
|
try:
|
|
out = xmltodict.unparse(out, pretty=True)
|
|
except ValueError as e:
|
|
logger.error('Failed to parse xml result')
|
|
try:
|
|
out['message'] = e
|
|
out['result'] = 'error'
|
|
out = xmltodict.unparse(out, pretty=True)
|
|
|
|
except Exception as e:
|
|
logger.error('Failed to parse xml result error message')
|
|
out = '''<?xml version="1.0" encoding="utf-8"?>
|
|
<response>
|
|
<message>%s</message>
|
|
<data></data>
|
|
<result>error</result>
|
|
</response>
|
|
''' % e
|
|
|
|
return out
|
|
|
|
def fetchData(self):
|
|
|
|
logger.info('Recieved API command: %s' % self.cmd)
|
|
if self.cmd and self.authenticated:
|
|
methodtocall = getattr(self, "_" + self.cmd)
|
|
# Let the traceback hit cherrypy so we can
|
|
# see the traceback there
|
|
if self.debug:
|
|
methodtocall(**self.kwargs)
|
|
else:
|
|
try:
|
|
methodtocall(**self.kwargs)
|
|
except Exception as e:
|
|
logger.error(traceback.format_exc())
|
|
|
|
# Im just lazy, fix me plx
|
|
if self.data or isinstance(self.data, (dict, list)):
|
|
if len(self.data):
|
|
self.result_type = 'success'
|
|
|
|
return self._out_as(self._responds(result_type=self.result_type, msg=self.msg, data=self.data))
|
|
|
|
def _dic_from_query(self, query):
|
|
|
|
db = database.MonitorDatabase()
|
|
rows = db.select(query)
|
|
|
|
rows_as_dic = []
|
|
|
|
for row in rows:
|
|
row_as_dic = dict(zip(row.keys(), row))
|
|
rows_as_dic.append(row_as_dic)
|
|
|
|
return rows_as_dic
|
|
|
|
def _getApikey(self, username='', password=''):
|
|
""" Returns api key, requires username and password is active """
|
|
|
|
apikey = hashlib.sha224(str(random.getrandbits(256))).hexdigest()[0:32]
|
|
if plexpy.CONFIG.HTTP_USERNAME and plexpy.CONFIG.HTTP_PASSWORD:
|
|
if username == plexpy.HTTP_USERNAME and password == plexpy.CONFIG.HTTP_PASSWORD:
|
|
if plexpy.CONFIG.API_KEY:
|
|
self.data = plexpy.CONFIG.API_KEY
|
|
else:
|
|
self.data = apikey
|
|
plexpy.CONFIG.API_KEY = apikey
|
|
plexpy.CONFIG.write()
|
|
else:
|
|
self.msg = 'Authentication is enabled, please add the correct username and password to the parameters'
|
|
else:
|
|
if plexpy.CONFIG.API_KEY:
|
|
self.data = plexpy.CONFIG.API_KEY
|
|
else:
|
|
# Make a apikey if the doesn't exist
|
|
self.data = apikey
|
|
plexpy.CONFIG.API_KEY = apikey
|
|
plexpy.CONFIG.write()
|
|
|
|
return self.data
|
|
|
|
def _getLogs(self, sort='', search='', order='desc', regex='', **kwargs):
|
|
"""
|
|
Returns the log
|
|
|
|
Returns [{"response":
|
|
{"msg": "Hey",
|
|
"result": "success"},
|
|
"data": [{"time": "29-sept.2015",
|
|
"thread: "MainThread",
|
|
"msg: "Called x from y",
|
|
"loglevel": "DEBUG"
|
|
}
|
|
]
|
|
|
|
}
|
|
]
|
|
"""
|
|
logfile = os.path.join(plexpy.CONFIG.LOG_DIR, logger.FILENAME)
|
|
templog = []
|
|
start = int(kwargs.get('start', 0))
|
|
end = int(kwargs.get('end', 0))
|
|
|
|
if regex:
|
|
logger.debug('Filtering log using regex %s' % regex)
|
|
reg = re.compile('u' + regex, flags=re.I)
|
|
|
|
for line in open(logfile, 'r').readlines():
|
|
temp_loglevel_and_time = None
|
|
|
|
try:
|
|
temp_loglevel_and_time = line.split('- ')
|
|
loglvl = temp_loglevel_and_time[1].split(' :')[0].strip()
|
|
tl_tread = line.split(' :: ')
|
|
if loglvl is None:
|
|
msg = line.replace('\n', '')
|
|
else:
|
|
msg = line.split(' : ')[1].replace('\n', '')
|
|
thread = tl_tread[1].split(' : ')[0]
|
|
except IndexError:
|
|
# We assume this is a traceback
|
|
tl = (len(templog) - 1)
|
|
templog[tl]['msg'] += line.replace('\n', '')
|
|
continue
|
|
|
|
if len(line) > 1 and temp_loglevel_and_time is not None and loglvl in line:
|
|
|
|
d = {
|
|
'time': temp_loglevel_and_time[0],
|
|
'loglevel': loglvl,
|
|
'msg': msg.replace('\n', ''),
|
|
'thread': thread
|
|
}
|
|
templog.append(d)
|
|
|
|
if end > 0:
|
|
logger.debug('Slicing the log from %s to %s' % (start, end))
|
|
templog = templog[start:end]
|
|
|
|
if sort:
|
|
logger.debug('Sorting log based on %s' % sort)
|
|
templog = sorted(templog, key=lambda k: k[sort])
|
|
|
|
if search:
|
|
logger.debug('Searching log values for %s' % search)
|
|
tt = [d for d in templog for k, v in d.items() if search.lower() in v.lower()]
|
|
|
|
if len(tt):
|
|
templog = tt
|
|
|
|
if regex:
|
|
tt = []
|
|
for l in templog:
|
|
stringdict = ' '.join('{}{}'.format(k, v) for k, v in l.items())
|
|
if reg.search(stringdict):
|
|
tt.append(l)
|
|
|
|
if len(tt):
|
|
templog = tt
|
|
|
|
if order == 'desc':
|
|
templog = templog[::-1]
|
|
|
|
self.data = templog
|
|
return templog
|
|
|
|
def _getVersion(self, **kwargs):
|
|
self.data = {
|
|
'git_path': plexpy.CONFIG.GIT_PATH,
|
|
'install_type': plexpy.INSTALL_TYPE,
|
|
'current_version': plexpy.CURRENT_VERSION,
|
|
'latest_version': plexpy.LATEST_VERSION,
|
|
'commits_behind': plexpy.COMMITS_BEHIND,
|
|
}
|
|
self.result_type = 'success'
|
|
|
|
def _checkGithub(self, **kwargs):
|
|
versioncheck.checkGithub()
|
|
self._getVersion()
|
|
|
|
def _shutdown(self, **kwargs):
|
|
plexpy.SIGNAL = 'shutdown'
|
|
self.msg = 'Shutting down plexpy'
|
|
self.result_type = 'success'
|
|
|
|
def _restart(self, **kwargs):
|
|
plexpy.SIGNAL = 'restart'
|
|
self.msg = 'Restarting plexpy'
|
|
self.result_type = 'success'
|
|
|
|
def _update(self, **kwargs):
|
|
plexpy.SIGNAL = 'update'
|
|
self.msg = 'Updating plexpy'
|
|
self.result_type = 'success'
|
|
|
|
def _getHistory(self, user=None, user_id=None, rating_key='', parent_rating_key='', grandparent_rating_key='', start_date='', **kwargs):
|
|
|
|
custom_where = []
|
|
if user_id:
|
|
custom_where = [['user_id', user_id]]
|
|
elif user:
|
|
custom_where = [['user', user]]
|
|
if 'rating_key' in kwargs:
|
|
rating_key = kwargs.get('rating_key', "")
|
|
custom_where = [['rating_key', rating_key]]
|
|
if 'parent_rating_key' in kwargs:
|
|
rating_key = kwargs.get('parent_rating_key', "")
|
|
custom_where = [['parent_rating_key', rating_key]]
|
|
if 'grandparent_rating_key' in kwargs:
|
|
rating_key = kwargs.get('grandparent_rating_key', "")
|
|
custom_where = [['grandparent_rating_key', rating_key]]
|
|
if 'start_date' in kwargs:
|
|
start_date = kwargs.get('start_date', "")
|
|
custom_where = [['strftime("%Y-%m-%d", datetime(date, "unixepoch", "localtime"))', start_date]]
|
|
|
|
data_factory = datafactory.DataFactory()
|
|
history = data_factory.get_datatables_history(kwargs=kwargs, custom_where=custom_where)
|
|
|
|
self.data = history
|
|
return self.data
|
|
|
|
def _getSync(self, machine_id=None, user_id=None, **kwargs):
|
|
|
|
pms_connect = pmsconnect.PmsConnect()
|
|
server_id = pms_connect.get_server_identity()
|
|
|
|
plex_tv = plextv.PlexTV()
|
|
if not machine_id:
|
|
result = plex_tv.get_synced_items(machine_id=server_id['machine_identifier'], user_id=user_id)
|
|
else:
|
|
result = plex_tv.get_synced_items(machine_id=machine_id, user_id=user_id)
|
|
|
|
if result:
|
|
self.data = result
|
|
return result
|
|
else:
|
|
self.msg = 'Unable to retrieve sync data for user'
|
|
logger.warn('Unable to retrieve sync data for user.')
|
|
|
|
def _getMetadata(self, rating_key='', **kwargs):
|
|
|
|
pms_connect = pmsconnect.PmsConnect()
|
|
result = pms_connect.get_metadata(rating_key, 'dict')
|
|
|
|
if result:
|
|
self.data = result
|
|
return result
|
|
else:
|
|
self.msg = 'Unable to retrive metadata %s' % rating_key
|
|
logger.warn('Unable to retrieve data.')
|
|
|
|
def _getSettings(self):
|
|
interface_dir = os.path.join(plexpy.PROG_DIR, 'data/interfaces/')
|
|
interface_list = [name for name in os.listdir(interface_dir) if
|
|
os.path.isdir(os.path.join(interface_dir, name))]
|
|
|
|
config = {
|
|
"http_host": plexpy.CONFIG.HTTP_HOST,
|
|
"http_username": plexpy.CONFIG.HTTP_USERNAME,
|
|
"http_port": plexpy.CONFIG.HTTP_PORT,
|
|
"http_password": plexpy.CONFIG.HTTP_PASSWORD,
|
|
"launch_browser": bool(plexpy.CONFIG.LAUNCH_BROWSER),
|
|
"enable_https": bool(plexpy.CONFIG.ENABLE_HTTPS),
|
|
"https_cert": plexpy.CONFIG.HTTPS_CERT,
|
|
"https_key": plexpy.CONFIG.HTTPS_KEY,
|
|
"api_enabled": plexpy.CONFIG.API_ENABLED,
|
|
"api_key": plexpy.CONFIG.API_KEY,
|
|
"update_db_interval": plexpy.CONFIG.UPDATE_DB_INTERVAL,
|
|
"freeze_db": bool(plexpy.CONFIG.FREEZE_DB),
|
|
"log_dir": plexpy.CONFIG.LOG_DIR,
|
|
"cache_dir": plexpy.CONFIG.CACHE_DIR,
|
|
"check_github": bool(plexpy.CONFIG.CHECK_GITHUB),
|
|
"interface_list": interface_list,
|
|
"cache_sizemb": plexpy.CONFIG.CACHE_SIZEMB,
|
|
"pms_identifier": plexpy.CONFIG.PMS_IDENTIFIER,
|
|
"pms_ip": plexpy.CONFIG.PMS_IP,
|
|
"pms_logs_folder": plexpy.CONFIG.PMS_LOGS_FOLDER,
|
|
"pms_port": plexpy.CONFIG.PMS_PORT,
|
|
"pms_token": plexpy.CONFIG.PMS_TOKEN,
|
|
"pms_ssl": bool(plexpy.CONFIG.PMS_SSL),
|
|
"pms_use_bif": bool(plexpy.CONFIG.PMS_USE_BIF),
|
|
"pms_uuid": plexpy.CONFIG.PMS_UUID,
|
|
"date_format": plexpy.CONFIG.DATE_FORMAT,
|
|
"time_format": plexpy.CONFIG.TIME_FORMAT,
|
|
"grouping_global_history": bool(plexpy.CONFIG.GROUPING_GLOBAL_HISTORY),
|
|
"grouping_user_history": bool(plexpy.CONFIG.GROUPING_USER_HISTORY),
|
|
"grouping_charts": bool(plexpy.CONFIG.GROUPING_CHARTS),
|
|
"movie_notify_enable": bool(plexpy.CONFIG.MOVIE_NOTIFY_ENABLE),
|
|
"tv_notify_enable": bool(plexpy.CONFIG.TV_NOTIFY_ENABLE),
|
|
"music_notify_enable": bool(plexpy.CONFIG.MUSIC_NOTIFY_ENABLE),
|
|
"tv_notify_on_start": bool(plexpy.CONFIG.TV_NOTIFY_ON_START),
|
|
"movie_notify_on_start": bool(plexpy.CONFIG.MOVIE_NOTIFY_ON_START),
|
|
"music_notify_on_start": bool(plexpy.CONFIG.MUSIC_NOTIFY_ON_START),
|
|
"tv_notify_on_stop": bool(plexpy.CONFIG.TV_NOTIFY_ON_STOP),
|
|
"movie_notify_on_stop": bool(plexpy.CONFIG.MOVIE_NOTIFY_ON_STOP),
|
|
"music_notify_on_stop": bool(plexpy.CONFIG.MUSIC_NOTIFY_ON_STOP),
|
|
"tv_notify_on_pause": bool(plexpy.CONFIG.TV_NOTIFY_ON_PAUSE),
|
|
"movie_notify_on_pause": bool(plexpy.CONFIG.MOVIE_NOTIFY_ON_PAUSE),
|
|
"music_notify_on_pause": bool(plexpy.CONFIG.MUSIC_NOTIFY_ON_PAUSE),
|
|
"monitoring_interval": plexpy.CONFIG.MONITORING_INTERVAL,
|
|
"refresh_users_interval": plexpy.CONFIG.REFRESH_USERS_INTERVAL,
|
|
"refresh_users_on_startup": bool(plexpy.CONFIG.REFRESH_USERS_ON_STARTUP),
|
|
"ip_logging_enable": bool(plexpy.CONFIG.IP_LOGGING_ENABLE),
|
|
"movie_logging_enable": bool(plexpy.CONFIG.MOVIE_LOGGING_ENABLE),
|
|
"tv_logging_enable": bool(plexpy.CONFIG.TV_LOGGING_ENABLE),
|
|
"music_logging_enable": bool(plexpy.CONFIG.MUSIC_LOGGING_ENABLE),
|
|
"logging_ignore_interval": plexpy.CONFIG.LOGGING_IGNORE_INTERVAL,
|
|
"pms_is_remote": bool(plexpy.CONFIG.PMS_IS_REMOTE),
|
|
"notify_watched_percent": plexpy.CONFIG.NOTIFY_WATCHED_PERCENT,
|
|
"notify_on_start_subject_text": plexpy.CONFIG.NOTIFY_ON_START_SUBJECT_TEXT,
|
|
"notify_on_start_body_text": plexpy.CONFIG.NOTIFY_ON_START_BODY_TEXT,
|
|
"notify_on_stop_subject_text": plexpy.CONFIG.NOTIFY_ON_STOP_SUBJECT_TEXT,
|
|
"notify_on_stop_body_text": plexpy.CONFIG.NOTIFY_ON_STOP_BODY_TEXT,
|
|
"notify_on_pause_subject_text": plexpy.CONFIG.NOTIFY_ON_PAUSE_SUBJECT_TEXT,
|
|
"notify_on_pause_body_text": plexpy.CONFIG.NOTIFY_ON_PAUSE_BODY_TEXT,
|
|
"notify_on_resume_subject_text": plexpy.CONFIG.NOTIFY_ON_RESUME_SUBJECT_TEXT,
|
|
"notify_on_resume_body_text": plexpy.CONFIG.NOTIFY_ON_RESUME_BODY_TEXT,
|
|
"notify_on_buffer_subject_text": plexpy.CONFIG.NOTIFY_ON_BUFFER_SUBJECT_TEXT,
|
|
"notify_on_buffer_body_text": plexpy.CONFIG.NOTIFY_ON_BUFFER_BODY_TEXT,
|
|
"notify_on_watched_subject_text": plexpy.CONFIG.NOTIFY_ON_WATCHED_SUBJECT_TEXT,
|
|
"notify_on_watched_body_text": plexpy.CONFIG.NOTIFY_ON_WATCHED_BODY_TEXT,
|
|
"home_stats_length": plexpy.CONFIG.HOME_STATS_LENGTH,
|
|
"home_stats_type": bool(plexpy.CONFIG.HOME_STATS_TYPE),
|
|
"home_stats_count": plexpy.CONFIG.HOME_STATS_COUNT,
|
|
"home_stats_cards": plexpy.CONFIG.HOME_STATS_CARDS,
|
|
"home_library_cards": plexpy.CONFIG.HOME_LIBRARY_CARDS,
|
|
"buffer_threshold": plexpy.CONFIG.BUFFER_THRESHOLD,
|
|
"buffer_wait": plexpy.CONFIG.BUFFER_WAIT
|
|
}
|
|
|
|
self.data = config
|
|
return config
|
|
|
|
def _getUserips(self, user_id=None, user=None, **kwargs):
|
|
custom_where = []
|
|
if user_id:
|
|
custom_where = [['user_id', user_id]]
|
|
elif user:
|
|
custom_where = [['user', user]]
|
|
|
|
user_data = users.Users()
|
|
history = user_data.get_user_unique_ips(kwargs=kwargs,
|
|
custom_where=custom_where)
|
|
|
|
if history:
|
|
self.data = history
|
|
return history
|
|
else:
|
|
self.msg = 'Failed to find users ips'
|
|
|
|
def _getPlayby(self, time_range='30', y_axis='plays', playtype='total_plays_per_month', **kwargs):
|
|
|
|
graph = graphs.Graphs()
|
|
if playtype == 'total_plays_per_month':
|
|
result = graph.get_total_plays_per_month(y_axis=y_axis)
|
|
|
|
elif playtype == 'total_plays_per_day':
|
|
result = graph.get_total_plays_per_day(time_range=time_range, y_axis=y_axis)
|
|
|
|
elif playtype == 'total_plays_per_hourofday':
|
|
result = graph.get_total_plays_per_hourofday(time_range=time_range, y_axis=y_axis)
|
|
|
|
elif playtype == 'total_plays_per_dayofweek':
|
|
result = graph.get_total_plays_per_dayofweek(time_range=time_range, y_axis=y_axis)
|
|
|
|
elif playtype == 'stream_type_by_top_10_users':
|
|
result = graph.get_stream_type_by_top_10_users(time_range=time_range, y_axis=y_axis)
|
|
|
|
elif playtype == 'stream_type_by_top_10_platforms':
|
|
result = graph.get_stream_type_by_top_10_platforms(time_range=time_range, y_axis=y_axis)
|
|
|
|
elif playtype == 'total_plays_by_stream_resolution':
|
|
result = graph.get_total_plays_by_stream_resolution(time_range=time_range, y_axis=y_axis)
|
|
|
|
elif playtype == 'total_plays_by_source_resolution':
|
|
result = graph.get_total_plays_by_source_resolution(time_range=time_range, y_axis=y_axis)
|
|
|
|
elif playtype == 'total_plays_per_stream_type':
|
|
result = graph.get_total_plays_per_stream_type(time_range=time_range, y_axis=y_axis)
|
|
|
|
elif playtype == 'total_plays_by_top_10_users':
|
|
result = graph.get_total_plays_by_top_10_users(time_range=time_range, y_axis=y_axis)
|
|
|
|
elif playtype == 'total_plays_by_top_10_platforms':
|
|
result = graph.get_total_plays_by_top_10_platforms(time_range=time_range, y_axis=y_axis)
|
|
|
|
if result:
|
|
self.data = result
|
|
return result
|
|
else:
|
|
logger.warn('Unable to retrieve %s from db' % playtype)
|