* 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"
|
||||
|
||||
from threading import Lock
|
||||
from lastfm.util import Wormhole, logging
|
||||
from lastfm.decorators import cached_property, async_callback
|
||||
_lock = Lock()
|
||||
|
||||
@ -23,6 +24,12 @@ class Api(object):
|
||||
"""The minimum interval between successive HTTP request, in seconds"""
|
||||
|
||||
SEARCH_XMLNS = "http://a9.com/-/spec/opensearch/1.1/"
|
||||
|
||||
DEBUG_LEVELS = {
|
||||
'LOW': 1,
|
||||
'MEDIUM': 2,
|
||||
'HIGH': 3
|
||||
}
|
||||
|
||||
def __init__(self,
|
||||
api_key,
|
||||
@ -31,7 +38,8 @@ class Api(object):
|
||||
input_encoding=None,
|
||||
request_headers=None,
|
||||
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
|
||||
starting point for accessing all the webservice methods.
|
||||
@ -64,8 +72,19 @@ class Api(object):
|
||||
self._initialize_user_agent()
|
||||
self._input_encoding = input_encoding
|
||||
self._no_cache = no_cache
|
||||
self._debug = debug
|
||||
self._logfile = logfile
|
||||
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
|
||||
def api_key(self):
|
||||
@ -588,6 +607,7 @@ class Api(object):
|
||||
"""
|
||||
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):
|
||||
# Break url into consituent parts
|
||||
(scheme, netloc, path, params, query, fragment) = urlparse.urlparse(url)
|
||||
@ -656,11 +676,10 @@ class Api(object):
|
||||
self._last_fetch_time = datetime.now()
|
||||
return url_data
|
||||
|
||||
@Wormhole.entrance('lfm-api-raw-data')
|
||||
def _fetch_url(self, url, parameters = None, no_cache = False):
|
||||
# Add key/value parameters to the query string of the url
|
||||
url = self._build_url(url, extra_params=parameters)
|
||||
if self._debug:
|
||||
print url
|
||||
# Get a url opener that can handle basic auth
|
||||
opener = self._get_opener(url)
|
||||
|
||||
@ -690,6 +709,7 @@ class Api(object):
|
||||
# Always return the latest version
|
||||
return url_data
|
||||
|
||||
@Wormhole.entrance('lfm-api-processed-data')
|
||||
def _fetch_data(self,
|
||||
params,
|
||||
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)
|
||||
return self._check_xml(xml)
|
||||
|
||||
@Wormhole.entrance('lfm-api-raw-data')
|
||||
def _post_url(self,
|
||||
url,
|
||||
parameters):
|
||||
url = self._build_url(url)
|
||||
data = self._encode_parameters(parameters)
|
||||
if self._debug:
|
||||
print data
|
||||
opener = self._get_opener(url)
|
||||
url_data = self._read_url_data(opener, url, data)
|
||||
return url_data
|
||||
|
||||
@Wormhole.entrance('lfm-api-processed-data')
|
||||
def _post_data(self, params):
|
||||
params['api_key'] = self.api_key
|
||||
|
||||
|
@ -8,6 +8,7 @@ __package__ = "lastfm"
|
||||
from functools import reduce
|
||||
from lastfm.base import LastfmBase
|
||||
from lastfm.mixin import cacheable
|
||||
from lastfm.util import logging
|
||||
from operator import xor
|
||||
|
||||
@cacheable
|
||||
@ -404,8 +405,8 @@ class RollingChart(Chart):
|
||||
try:
|
||||
period_wacl.append(
|
||||
getattr(subject, "get_weekly_%s_chart" % chart_type)(wc.start, wc.end))
|
||||
except LastfmError:
|
||||
pass
|
||||
except LastfmError as ex:
|
||||
logging.log_silenced_exceptions(ex)
|
||||
stats_dict = period_wacl[0].__dict__["_%ss" % chart_type][0].stats.__dict__
|
||||
count_attribute = [k for k in stats_dict.keys()
|
||||
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"
|
||||
__package__ = "lastfm.mixin"
|
||||
|
||||
from lastfm.util import lazylist
|
||||
from lastfm.util import lazylist, logging
|
||||
from lastfm.decorators import cached_property
|
||||
|
||||
def chartable(chart_types):
|
||||
@ -83,8 +83,8 @@ def chartable(chart_types):
|
||||
for wc in wcl:
|
||||
try:
|
||||
yield self.get_weekly_album_chart(wc.start, wc.end)
|
||||
except LastfmError:
|
||||
pass
|
||||
except LastfmError as ex:
|
||||
logging.log_silenced_exceptions(ex)
|
||||
return gen()
|
||||
|
||||
def get_monthly_album_chart(self, start = None, end = None):
|
||||
@ -104,8 +104,8 @@ def chartable(chart_types):
|
||||
for mc in mcl:
|
||||
try:
|
||||
yield self.get_monthly_album_chart(mc.start, mc.end)
|
||||
except LastfmError:
|
||||
pass
|
||||
except LastfmError as ex:
|
||||
logging.log_silenced_exceptions(ex)
|
||||
return gen()
|
||||
|
||||
def get_quaterly_album_chart(self, start = None, end = None):
|
||||
@ -179,8 +179,8 @@ def chartable(chart_types):
|
||||
for wc in wcl:
|
||||
try:
|
||||
yield self.get_weekly_artist_chart(wc.start, wc.end)
|
||||
except LastfmError:
|
||||
pass
|
||||
except LastfmError as ex:
|
||||
logging.log_silenced_exceptions(ex)
|
||||
return gen()
|
||||
|
||||
def get_monthly_artist_chart(self, start = None, end = None):
|
||||
@ -200,8 +200,8 @@ def chartable(chart_types):
|
||||
for mc in mcl:
|
||||
try:
|
||||
yield self.get_monthly_artist_chart(mc.start, mc.end)
|
||||
except LastfmError:
|
||||
pass
|
||||
except LastfmError as ex:
|
||||
logging.log_silenced_exceptions(ex)
|
||||
return gen()
|
||||
|
||||
def get_quaterly_artist_chart(self, start = None, end = None):
|
||||
@ -275,8 +275,8 @@ def chartable(chart_types):
|
||||
for wc in wcl:
|
||||
try:
|
||||
yield self.get_weekly_track_chart(wc.start, wc.end)
|
||||
except LastfmError:
|
||||
pass
|
||||
except LastfmError as ex:
|
||||
logging.log_silenced_exceptions(ex)
|
||||
return gen()
|
||||
|
||||
def get_monthly_track_chart(self, start = None, end = None):
|
||||
@ -296,8 +296,8 @@ def chartable(chart_types):
|
||||
for mc in mcl:
|
||||
try:
|
||||
yield self.get_monthly_track_chart(mc.start, mc.end)
|
||||
except LastfmError:
|
||||
pass
|
||||
except LastfmError as ex:
|
||||
logging.log_silenced_exceptions(ex)
|
||||
return gen()
|
||||
|
||||
def get_quaterly_track_chart(self, start = None, end = None):
|
||||
@ -373,8 +373,8 @@ def chartable(chart_types):
|
||||
for wc in wcl:
|
||||
try:
|
||||
yield self.get_weekly_tag_chart(wc.start, wc.end)
|
||||
except LastfmError:
|
||||
pass
|
||||
except LastfmError as ex:
|
||||
logging.log_silenced_exceptions(ex)
|
||||
return gen()
|
||||
|
||||
def get_monthly_tag_chart(self, start = None, end = None):
|
||||
@ -394,8 +394,8 @@ def chartable(chart_types):
|
||||
for mc in mcl:
|
||||
try:
|
||||
yield self.get_monthly_tag_chart(mc.start, mc.end)
|
||||
except LastfmError:
|
||||
pass
|
||||
except LastfmError as ex:
|
||||
logging.log_silenced_exceptions(ex)
|
||||
return gen()
|
||||
|
||||
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"
|
||||
__license__ = "GNU Lesser General Public License"
|
||||
__package__ = "lastfm.util"
|
||||
|
||||
from lastfm.util import Wormhole
|
||||
|
||||
_registry = {}
|
||||
|
||||
@ -24,6 +26,7 @@ class ObjectCache(object):
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
@Wormhole.entrance('lfm-obcache-register')
|
||||
def register(ob, key):
|
||||
cls_name = ob.__class__.__name__
|
||||
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