* added wormhole and logging modules.
This commit is contained in:
parent
e0c0277336
commit
bc726dddc9
@ -7,6 +7,7 @@ __license__ = "GNU Lesser General Public License"
|
|||||||
__package__ = "lastfm"
|
__package__ = "lastfm"
|
||||||
|
|
||||||
from threading import Lock
|
from threading import Lock
|
||||||
|
from lastfm.util import Wormhole, logging
|
||||||
from lastfm.decorators import cached_property, async_callback
|
from lastfm.decorators import cached_property, async_callback
|
||||||
_lock = Lock()
|
_lock = Lock()
|
||||||
|
|
||||||
@ -23,6 +24,12 @@ class Api(object):
|
|||||||
"""The minimum interval between successive HTTP request, in seconds"""
|
"""The minimum interval between successive HTTP request, in seconds"""
|
||||||
|
|
||||||
SEARCH_XMLNS = "http://a9.com/-/spec/opensearch/1.1/"
|
SEARCH_XMLNS = "http://a9.com/-/spec/opensearch/1.1/"
|
||||||
|
|
||||||
|
DEBUG_LEVELS = {
|
||||||
|
'LOW': 1,
|
||||||
|
'MEDIUM': 2,
|
||||||
|
'HIGH': 3
|
||||||
|
}
|
||||||
|
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
api_key,
|
api_key,
|
||||||
@ -31,7 +38,8 @@ class Api(object):
|
|||||||
input_encoding=None,
|
input_encoding=None,
|
||||||
request_headers=None,
|
request_headers=None,
|
||||||
no_cache = False,
|
no_cache = False,
|
||||||
debug = False):
|
debug = None,
|
||||||
|
logfile = None):
|
||||||
"""
|
"""
|
||||||
Create an Api object to access the last.fm webservice API. Use this object as a
|
Create an Api object to access the last.fm webservice API. Use this object as a
|
||||||
starting point for accessing all the webservice methods.
|
starting point for accessing all the webservice methods.
|
||||||
@ -64,8 +72,19 @@ class Api(object):
|
|||||||
self._initialize_user_agent()
|
self._initialize_user_agent()
|
||||||
self._input_encoding = input_encoding
|
self._input_encoding = input_encoding
|
||||||
self._no_cache = no_cache
|
self._no_cache = no_cache
|
||||||
self._debug = debug
|
self._logfile = logfile
|
||||||
self._last_fetch_time = datetime.now()
|
self._last_fetch_time = datetime.now()
|
||||||
|
|
||||||
|
if debug is not None:
|
||||||
|
if debug in Api.DEBUG_LEVELS:
|
||||||
|
self._debug = Api.DEBUG_LEVELS[debug]
|
||||||
|
else:
|
||||||
|
raise InvalidParametersError("debug parameter must be one of the keys in Api.DEBUG_LEVELS dict")
|
||||||
|
else:
|
||||||
|
self._debug = None
|
||||||
|
if self._debug is not None:
|
||||||
|
Wormhole.enable()
|
||||||
|
logging.set_api(self)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def api_key(self):
|
def api_key(self):
|
||||||
@ -588,6 +607,7 @@ class Api(object):
|
|||||||
"""
|
"""
|
||||||
return Venue.search(self, search_item = venue, limit = limit, country = country)
|
return Venue.search(self, search_item = venue, limit = limit, country = country)
|
||||||
|
|
||||||
|
@Wormhole.entrance('lfm-api-url')
|
||||||
def _build_url(self, url, path_elements=None, extra_params=None):
|
def _build_url(self, url, path_elements=None, extra_params=None):
|
||||||
# Break url into consituent parts
|
# Break url into consituent parts
|
||||||
(scheme, netloc, path, params, query, fragment) = urlparse.urlparse(url)
|
(scheme, netloc, path, params, query, fragment) = urlparse.urlparse(url)
|
||||||
@ -656,11 +676,10 @@ class Api(object):
|
|||||||
self._last_fetch_time = datetime.now()
|
self._last_fetch_time = datetime.now()
|
||||||
return url_data
|
return url_data
|
||||||
|
|
||||||
|
@Wormhole.entrance('lfm-api-raw-data')
|
||||||
def _fetch_url(self, url, parameters = None, no_cache = False):
|
def _fetch_url(self, url, parameters = None, no_cache = False):
|
||||||
# Add key/value parameters to the query string of the url
|
# Add key/value parameters to the query string of the url
|
||||||
url = self._build_url(url, extra_params=parameters)
|
url = self._build_url(url, extra_params=parameters)
|
||||||
if self._debug:
|
|
||||||
print url
|
|
||||||
# Get a url opener that can handle basic auth
|
# Get a url opener that can handle basic auth
|
||||||
opener = self._get_opener(url)
|
opener = self._get_opener(url)
|
||||||
|
|
||||||
@ -690,6 +709,7 @@ class Api(object):
|
|||||||
# Always return the latest version
|
# Always return the latest version
|
||||||
return url_data
|
return url_data
|
||||||
|
|
||||||
|
@Wormhole.entrance('lfm-api-processed-data')
|
||||||
def _fetch_data(self,
|
def _fetch_data(self,
|
||||||
params,
|
params,
|
||||||
sign = False,
|
sign = False,
|
||||||
@ -710,17 +730,17 @@ class Api(object):
|
|||||||
xml = self._fetch_url(Api.API_ROOT_URL, params, no_cache = self._no_cache or no_cache)
|
xml = self._fetch_url(Api.API_ROOT_URL, params, no_cache = self._no_cache or no_cache)
|
||||||
return self._check_xml(xml)
|
return self._check_xml(xml)
|
||||||
|
|
||||||
|
@Wormhole.entrance('lfm-api-raw-data')
|
||||||
def _post_url(self,
|
def _post_url(self,
|
||||||
url,
|
url,
|
||||||
parameters):
|
parameters):
|
||||||
url = self._build_url(url)
|
url = self._build_url(url)
|
||||||
data = self._encode_parameters(parameters)
|
data = self._encode_parameters(parameters)
|
||||||
if self._debug:
|
|
||||||
print data
|
|
||||||
opener = self._get_opener(url)
|
opener = self._get_opener(url)
|
||||||
url_data = self._read_url_data(opener, url, data)
|
url_data = self._read_url_data(opener, url, data)
|
||||||
return url_data
|
return url_data
|
||||||
|
|
||||||
|
@Wormhole.entrance('lfm-api-processed-data')
|
||||||
def _post_data(self, params):
|
def _post_data(self, params):
|
||||||
params['api_key'] = self.api_key
|
params['api_key'] = self.api_key
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ __package__ = "lastfm"
|
|||||||
from functools import reduce
|
from functools import reduce
|
||||||
from lastfm.base import LastfmBase
|
from lastfm.base import LastfmBase
|
||||||
from lastfm.mixin import cacheable
|
from lastfm.mixin import cacheable
|
||||||
|
from lastfm.util import logging
|
||||||
from operator import xor
|
from operator import xor
|
||||||
|
|
||||||
@cacheable
|
@cacheable
|
||||||
@ -404,8 +405,8 @@ class RollingChart(Chart):
|
|||||||
try:
|
try:
|
||||||
period_wacl.append(
|
period_wacl.append(
|
||||||
getattr(subject, "get_weekly_%s_chart" % chart_type)(wc.start, wc.end))
|
getattr(subject, "get_weekly_%s_chart" % chart_type)(wc.start, wc.end))
|
||||||
except LastfmError:
|
except LastfmError as ex:
|
||||||
pass
|
logging.log_silenced_exceptions(ex)
|
||||||
stats_dict = period_wacl[0].__dict__["_%ss" % chart_type][0].stats.__dict__
|
stats_dict = period_wacl[0].__dict__["_%ss" % chart_type][0].stats.__dict__
|
||||||
count_attribute = [k for k in stats_dict.keys()
|
count_attribute = [k for k in stats_dict.keys()
|
||||||
if stats_dict[k] is not None and k not in ['_rank', '_subject']][0]
|
if stats_dict[k] is not None and k not in ['_rank', '_subject']][0]
|
||||||
|
@ -5,7 +5,7 @@ __version__ = "0.2"
|
|||||||
__license__ = "GNU Lesser General Public License"
|
__license__ = "GNU Lesser General Public License"
|
||||||
__package__ = "lastfm.mixin"
|
__package__ = "lastfm.mixin"
|
||||||
|
|
||||||
from lastfm.util import lazylist
|
from lastfm.util import lazylist, logging
|
||||||
from lastfm.decorators import cached_property
|
from lastfm.decorators import cached_property
|
||||||
|
|
||||||
def chartable(chart_types):
|
def chartable(chart_types):
|
||||||
@ -83,8 +83,8 @@ def chartable(chart_types):
|
|||||||
for wc in wcl:
|
for wc in wcl:
|
||||||
try:
|
try:
|
||||||
yield self.get_weekly_album_chart(wc.start, wc.end)
|
yield self.get_weekly_album_chart(wc.start, wc.end)
|
||||||
except LastfmError:
|
except LastfmError as ex:
|
||||||
pass
|
logging.log_silenced_exceptions(ex)
|
||||||
return gen()
|
return gen()
|
||||||
|
|
||||||
def get_monthly_album_chart(self, start = None, end = None):
|
def get_monthly_album_chart(self, start = None, end = None):
|
||||||
@ -104,8 +104,8 @@ def chartable(chart_types):
|
|||||||
for mc in mcl:
|
for mc in mcl:
|
||||||
try:
|
try:
|
||||||
yield self.get_monthly_album_chart(mc.start, mc.end)
|
yield self.get_monthly_album_chart(mc.start, mc.end)
|
||||||
except LastfmError:
|
except LastfmError as ex:
|
||||||
pass
|
logging.log_silenced_exceptions(ex)
|
||||||
return gen()
|
return gen()
|
||||||
|
|
||||||
def get_quaterly_album_chart(self, start = None, end = None):
|
def get_quaterly_album_chart(self, start = None, end = None):
|
||||||
@ -179,8 +179,8 @@ def chartable(chart_types):
|
|||||||
for wc in wcl:
|
for wc in wcl:
|
||||||
try:
|
try:
|
||||||
yield self.get_weekly_artist_chart(wc.start, wc.end)
|
yield self.get_weekly_artist_chart(wc.start, wc.end)
|
||||||
except LastfmError:
|
except LastfmError as ex:
|
||||||
pass
|
logging.log_silenced_exceptions(ex)
|
||||||
return gen()
|
return gen()
|
||||||
|
|
||||||
def get_monthly_artist_chart(self, start = None, end = None):
|
def get_monthly_artist_chart(self, start = None, end = None):
|
||||||
@ -200,8 +200,8 @@ def chartable(chart_types):
|
|||||||
for mc in mcl:
|
for mc in mcl:
|
||||||
try:
|
try:
|
||||||
yield self.get_monthly_artist_chart(mc.start, mc.end)
|
yield self.get_monthly_artist_chart(mc.start, mc.end)
|
||||||
except LastfmError:
|
except LastfmError as ex:
|
||||||
pass
|
logging.log_silenced_exceptions(ex)
|
||||||
return gen()
|
return gen()
|
||||||
|
|
||||||
def get_quaterly_artist_chart(self, start = None, end = None):
|
def get_quaterly_artist_chart(self, start = None, end = None):
|
||||||
@ -275,8 +275,8 @@ def chartable(chart_types):
|
|||||||
for wc in wcl:
|
for wc in wcl:
|
||||||
try:
|
try:
|
||||||
yield self.get_weekly_track_chart(wc.start, wc.end)
|
yield self.get_weekly_track_chart(wc.start, wc.end)
|
||||||
except LastfmError:
|
except LastfmError as ex:
|
||||||
pass
|
logging.log_silenced_exceptions(ex)
|
||||||
return gen()
|
return gen()
|
||||||
|
|
||||||
def get_monthly_track_chart(self, start = None, end = None):
|
def get_monthly_track_chart(self, start = None, end = None):
|
||||||
@ -296,8 +296,8 @@ def chartable(chart_types):
|
|||||||
for mc in mcl:
|
for mc in mcl:
|
||||||
try:
|
try:
|
||||||
yield self.get_monthly_track_chart(mc.start, mc.end)
|
yield self.get_monthly_track_chart(mc.start, mc.end)
|
||||||
except LastfmError:
|
except LastfmError as ex:
|
||||||
pass
|
logging.log_silenced_exceptions(ex)
|
||||||
return gen()
|
return gen()
|
||||||
|
|
||||||
def get_quaterly_track_chart(self, start = None, end = None):
|
def get_quaterly_track_chart(self, start = None, end = None):
|
||||||
@ -373,8 +373,8 @@ def chartable(chart_types):
|
|||||||
for wc in wcl:
|
for wc in wcl:
|
||||||
try:
|
try:
|
||||||
yield self.get_weekly_tag_chart(wc.start, wc.end)
|
yield self.get_weekly_tag_chart(wc.start, wc.end)
|
||||||
except LastfmError:
|
except LastfmError as ex:
|
||||||
pass
|
logging.log_silenced_exceptions(ex)
|
||||||
return gen()
|
return gen()
|
||||||
|
|
||||||
def get_monthly_tag_chart(self, start = None, end = None):
|
def get_monthly_tag_chart(self, start = None, end = None):
|
||||||
@ -394,8 +394,8 @@ def chartable(chart_types):
|
|||||||
for mc in mcl:
|
for mc in mcl:
|
||||||
try:
|
try:
|
||||||
yield self.get_monthly_tag_chart(mc.start, mc.end)
|
yield self.get_monthly_tag_chart(mc.start, mc.end)
|
||||||
except LastfmError:
|
except LastfmError as ex:
|
||||||
pass
|
logging.log_silenced_exceptions(ex)
|
||||||
return gen()
|
return gen()
|
||||||
|
|
||||||
def get_quaterly_tag_chart(self, start = None, end = None):
|
def get_quaterly_tag_chart(self, start = None, end = None):
|
||||||
|
65
lastfm/util/logging.py
Normal file
65
lastfm/util/logging.py
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
__author__ = "Abhinav Sarkar <abhinav@abhinavsarkar.net>"
|
||||||
|
__version__ = "0.2"
|
||||||
|
__license__ = "GNU Lesser General Public License"
|
||||||
|
__package__ = "lastfm.util"
|
||||||
|
|
||||||
|
from contextlib import contextmanager, nested
|
||||||
|
from threading import Lock
|
||||||
|
from lastfm.util import Wormhole
|
||||||
|
from datetime import datetime
|
||||||
|
import sys
|
||||||
|
|
||||||
|
api = None
|
||||||
|
lock = Lock()
|
||||||
|
|
||||||
|
def set_api(api_):
|
||||||
|
global api
|
||||||
|
api = api_
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def logfile():
|
||||||
|
if api._logfile is None:
|
||||||
|
try:
|
||||||
|
yield sys.stdout
|
||||||
|
finally:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
log = None
|
||||||
|
try:
|
||||||
|
log = open(api._logfile, 'at')
|
||||||
|
yield log
|
||||||
|
except IOError:
|
||||||
|
sys.stderr.write("could not open log file, logging to stdout\n")
|
||||||
|
yield sys.stdout
|
||||||
|
finally:
|
||||||
|
if log is not None:
|
||||||
|
log.close()
|
||||||
|
|
||||||
|
@Wormhole.exit('lfm-api-url')
|
||||||
|
def log_url(url, *args, **kwargs):
|
||||||
|
if api._debug >= api.DEBUG_LEVELS['LOW']:
|
||||||
|
with nested(logfile(), lock) as (log, l):
|
||||||
|
log.write("{0}: URL fetched: {1}\n".format(datetime.now(), url))
|
||||||
|
|
||||||
|
@Wormhole.exit('lfm-obcache-register')
|
||||||
|
def log_object_registration((inst, already_registered), *args, **kwargs):
|
||||||
|
if api._debug >= api.DEBUG_LEVELS['MEDIUM']:
|
||||||
|
with nested(logfile(), lock) as (log, l):
|
||||||
|
if already_registered:
|
||||||
|
log.write("{0}: already registered: {1}\n".format(datetime.now(), repr(inst)))
|
||||||
|
else:
|
||||||
|
log.write("{0}: not already registered: {1}\n".format(datetime.now(), inst.__class__))
|
||||||
|
|
||||||
|
@Wormhole.exit('lfm-api-raw-data')
|
||||||
|
def log_raw_data(raw_data, *args, **kwargs):
|
||||||
|
if api._debug >= api.DEBUG_LEVELS['HIGH']:
|
||||||
|
with nested(logfile(), lock) as (log, l):
|
||||||
|
log.write("{0}: RAW DATA\n {1}\n".format(datetime.now(), raw_data))
|
||||||
|
|
||||||
|
def log_silenced_exceptions(ex):
|
||||||
|
if api._debug >= api.DEBUG_LEVELS['LOW']:
|
||||||
|
with nested(logfile(), lock) as (log, l):
|
||||||
|
log.write("{0}: Silenced Exception: {1}\n".format(datetime.now(), ex))
|
@ -4,6 +4,8 @@ __author__ = "Abhinav Sarkar <abhinav@abhinavsarkar.net>"
|
|||||||
__version__ = "0.2"
|
__version__ = "0.2"
|
||||||
__license__ = "GNU Lesser General Public License"
|
__license__ = "GNU Lesser General Public License"
|
||||||
__package__ = "lastfm.util"
|
__package__ = "lastfm.util"
|
||||||
|
|
||||||
|
from lastfm.util import Wormhole
|
||||||
|
|
||||||
_registry = {}
|
_registry = {}
|
||||||
|
|
||||||
@ -24,6 +26,7 @@ class ObjectCache(object):
|
|||||||
]
|
]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@Wormhole.entrance('lfm-obcache-register')
|
||||||
def register(ob, key):
|
def register(ob, key):
|
||||||
cls_name = ob.__class__.__name__
|
cls_name = ob.__class__.__name__
|
||||||
if not cls_name in _registry:
|
if not cls_name in _registry:
|
||||||
|
89
lastfm/util/wormhole.py
Normal file
89
lastfm/util/wormhole.py
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
__author__ = "Abhinav Sarkar <abhinav@abhinavsarkar.net>"
|
||||||
|
__version__ = "0.2"
|
||||||
|
__license__ = "GNU Lesser General Public License"
|
||||||
|
__package__ = "lastfm.util"
|
||||||
|
|
||||||
|
from collections import defaultdict
|
||||||
|
from decorator import decorator
|
||||||
|
from threading import Lock
|
||||||
|
|
||||||
|
_lock = Lock()
|
||||||
|
|
||||||
|
class Wormhole(object):
|
||||||
|
_entrances = defaultdict(set)
|
||||||
|
_exits = defaultdict(set)
|
||||||
|
_enabled = False
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def disable():
|
||||||
|
with _lock:
|
||||||
|
Wormhole._enabled = False
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def enable():
|
||||||
|
with _lock:
|
||||||
|
Wormhole._enabled = True
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def add_entrance(topic, entrance):
|
||||||
|
wrapped = Wormhole.entrance(topic)(entrance)
|
||||||
|
wrapped._orginal = entrance
|
||||||
|
return wrapped
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def add_exit(topic, exit):
|
||||||
|
Wormhole._exits[topic].add(exit)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def remove_entrance(topic, entrance):
|
||||||
|
entrance = entrance._orginal
|
||||||
|
if topic in Wormhole._entrances:
|
||||||
|
if entrance in Wormhole._entrances[topic]:
|
||||||
|
Wormhole._entrances[topic].remove(entrance)
|
||||||
|
return entrance
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def remove_exit(topic, exit):
|
||||||
|
if topic in Wormhole._exits:
|
||||||
|
if exit in Wormhole._exits[topic]:
|
||||||
|
Wormhole._exits[topic].remove(exit)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def entrance(cls, topic):
|
||||||
|
@decorator
|
||||||
|
def wrapper(func, *args, **kwargs):
|
||||||
|
Wormhole._entrances[topic].add(func)
|
||||||
|
retval = func(*args, **kwargs)
|
||||||
|
if topic in Wormhole._exits:
|
||||||
|
if Wormhole._enabled:
|
||||||
|
if func in Wormhole._entrances[topic]:
|
||||||
|
cls._jump(topic, retval, *args, **kwargs)
|
||||||
|
return retval
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def exit(topic):
|
||||||
|
def wrapper(func):
|
||||||
|
Wormhole._exits[topic].add(func)
|
||||||
|
return func
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _jump(topic, retval, *args, **kwargs):
|
||||||
|
exceptions = []
|
||||||
|
for f in Wormhole._exits[topic]:
|
||||||
|
try:
|
||||||
|
f(retval, *args, **kwargs)
|
||||||
|
except Exception as e:
|
||||||
|
exceptions.append(e)
|
||||||
|
for e in exceptions:
|
||||||
|
raise e
|
||||||
|
|
||||||
|
class ThreadedWormhole(Wormhole):
|
||||||
|
@staticmethod
|
||||||
|
def _jump(topic, retval, *args, **kwargs):
|
||||||
|
import threading
|
||||||
|
for f in Wormhole._exits[topic]:
|
||||||
|
threading.Thread(target = lambda: f(retval, *args, **kwargs)).start()
|
Loading…
Reference in New Issue
Block a user