* 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" __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

View File

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

View File

@ -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
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" __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
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()