* added wormhole and logging modules.

master
Abhinav Sarkar 2009-04-12 05:07:41 +00:00
parent e0c0277336
commit bc726dddc9
6 changed files with 203 additions and 25 deletions

View File

@ -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

View File

@ -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]

View File

@ -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
View 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))

View File

@ -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
View 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()