Projects
home:hennevogel
python-tvdb_api
Sign Up
Log In
Username
Password
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
Expand all
Collapse all
Changes of Revision 2
View file
python-tvdb_api.changes
Changed
@@ -1,4 +1,9 @@ ------------------------------------------------------------------- +Mon Feb 4 14:26:00 UTC 2013 - hvogel@opensuse.org + +- Update to version 1.8.2 + +------------------------------------------------------------------- Wed May 5 13:35:49 UTC 2010 - henne@links2linux.de - Update to version 1.4
View file
python-tvdb_api.spec
Changed
@@ -2,9 +2,9 @@ Name: python-tvdb_api Summary: Python module to access the API from thetvdb.com License: GPL -Url: http://wiki.github.com/dbr/tvdb_api +Url: http://pypi.python.org/pypi/tvdb_api Group: Productivity/Multimedia/Other -Version: 1.5 +Version: 1.8.2 Release: 1 Source0: tvdb_api-%version.tar.bz2 BuildRoot: %{_tmppath}/%{name}-buildroot
View file
tvdb_api-1.5.tar.bz2/.gitignore
Deleted
@@ -1,2 +0,0 @@ -.DS_Store -*.pyc
View file
tvdb_api-1.5.tar.bz2/cache.py
Deleted
@@ -1,232 +0,0 @@ -#!/usr/bin/env python -#encoding:utf-8 -#author:dbr/Ben -#project:tvdb_api -#repository:http://github.com/dbr/tvdb_api -#license:Creative Commons GNU GPL v2 -# (http://creativecommons.org/licenses/GPL/2.0/) - -""" -urllib2 caching handler -Modified from http://code.activestate.com/recipes/491261/ -""" -from __future__ import with_statement - -__author__ = "dbr/Ben" -__version__ = "1.5" - -import os -import time -import errno -import httplib -import urllib2 -import StringIO -from hashlib import md5 -from threading import RLock - -cache_lock = RLock() - -def locked_function(origfunc): - """Decorator to execute function under lock""" - def wrapped(*args, **kwargs): - cache_lock.acquire() - try: - return origfunc(*args, **kwargs) - finally: - cache_lock.release() - return wrapped - -def calculate_cache_path(cache_location, url): - """Checks if [cache_location]/[hash_of_url].headers and .body exist - """ - thumb = md5(url).hexdigest() - header = os.path.join(cache_location, thumb + ".headers") - body = os.path.join(cache_location, thumb + ".body") - return header, body - -def check_cache_time(path, max_age): - """Checks if a file has been created/modified in the [last max_age] seconds. - False means the file is too old (or doesn't exist), True means it is - up-to-date and valid""" - if not os.path.isfile(path): - return False - cache_modified_time = os.stat(path).st_mtime - time_now = time.time() - if cache_modified_time < time_now - max_age: - # Cache is old - return False - else: - return True - -@locked_function -def exists_in_cache(cache_location, url, max_age): - """Returns if header AND body cache file exist (and are up-to-date)""" - hpath, bpath = calculate_cache_path(cache_location, url) - if os.path.exists(hpath) and os.path.exists(bpath): - return( - check_cache_time(hpath, max_age) - and check_cache_time(bpath, max_age) - ) - else: - # File does not exist - return False - -@locked_function -def store_in_cache(cache_location, url, response): - """Tries to store response in cache.""" - hpath, bpath = calculate_cache_path(cache_location, url) - try: - outf = open(hpath, "w") - headers = str(response.info()) - outf.write(headers) - outf.close() - - outf = open(bpath, "w") - outf.write(response.read()) - outf.close() - except IOError: - return True - else: - return False - -class CacheHandler(urllib2.BaseHandler): - """Stores responses in a persistant on-disk cache. - - If a subsequent GET request is made for the same URL, the stored - response is returned, saving time, resources and bandwidth - """ - @locked_function - def __init__(self, cache_location, max_age = 21600): - """The location of the cache directory""" - self.max_age = max_age - self.cache_location = cache_location - if not os.path.exists(self.cache_location): - try: - os.mkdir(self.cache_location) - except OSError, e: - if e.errno == errno.EEXIST and os.path.isdir(self.cache_location): - # File exists, and it's a directory, - # another process beat us to creating this dir, that's OK. - pass - else: - # Our target dir is already a file, or different error, - # relay the error! - raise - - def default_open(self, request): - """Handles GET requests, if the response is cached it returns it - """ - if request.get_method() is not "GET": - return None # let the next handler try to handle the request - - if exists_in_cache( - self.cache_location, request.get_full_url(), self.max_age - ): - return CachedResponse( - self.cache_location, - request.get_full_url(), - set_cache_header = True - ) - else: - return None - - def http_response(self, request, response): - """Gets a HTTP response, if it was a GET request and the status code - starts with 2 (200 OK etc) it caches it and returns a CachedResponse - """ - if (request.get_method() == "GET" - and str(response.code).startswith("2") - ): - if 'x-local-cache' not in response.info(): - # Response is not cached - set_cache_header = store_in_cache( - self.cache_location, - request.get_full_url(), - response - ) - else: - set_cache_header = True - #end if x-cache in response - - return CachedResponse( - self.cache_location, - request.get_full_url(), - set_cache_header = set_cache_header - ) - else: - return response - -class CachedResponse(StringIO.StringIO): - """An urllib2.response-like object for cached responses. - - To determine if a response is cached or coming directly from - the network, check the x-local-cache header rather than the object type. - """ - - @locked_function - def __init__(self, cache_location, url, set_cache_header=True): - self.cache_location = cache_location - hpath, bpath = calculate_cache_path(cache_location, url) - - StringIO.StringIO.__init__(self, file(bpath).read()) - - self.url = url - self.code = 200 - self.msg = "OK" - headerbuf = file(hpath).read() - if set_cache_header: - headerbuf += "x-local-cache: %s\r\n" % (bpath) - self.headers = httplib.HTTPMessage(StringIO.StringIO(headerbuf)) - - def info(self): - """Returns headers - """ - return self.headers - - def geturl(self): - """Returns original URL - """ - return self.url - - @locked_function - def recache(self): - new_request = urllib2.urlopen(self.url) - set_cache_header = store_in_cache( - self.cache_location, - new_request.url, - new_request - ) - CachedResponse.__init__(self, self.cache_location, self.url, True) - - -if __name__ == "__main__": - def main(): - """Quick test/example of CacheHandler""" - opener = urllib2.build_opener(CacheHandler("/tmp/")) - response = opener.open("http://google.com") - print response.headers - print "Response:", response.read() - - response.recache() - print response.headers - print "After recache:", response.read() - - # Test usage in threads - from threading import Thread - class CacheThreadTest(Thread): - lastdata = None - def run(self): - req = opener.open("http://google.com") - newdata = req.read() - if self.lastdata is None: - self.lastdata = newdata - assert self.lastdata == newdata, "Data was not consistent, uhoh" - req.recache() - threads = [CacheThreadTest() for x in range(50)] - print "Starting threads" - [t.start() for t in threads] - print "..done" - print "Joining threads" - [t.join() for t in threads] - print "..done" - main()
View file
tvdb_api-1.8.2.tar.gz/MANIFEST.in
Added
@@ -0,0 +1,4 @@ +include UNLICENSE +include readme.md +include tests/*.py +include Rakefile
View file
tvdb_api-1.8.2.tar.gz/PKG-INFO
Added
@@ -0,0 +1,27 @@ +Metadata-Version: 1.0 +Name: tvdb_api +Version: 1.8.2 +Summary: Interface to thetvdb.com +Home-page: http://github.com/dbr/tvdb_api/tree/master +Author: dbr/Ben +Author-email: UNKNOWN +License: unlicense +Description: An easy to use API interface to TheTVDB.com + Basic usage is: + + >>> import tvdb_api + >>> t = tvdb_api.Tvdb() + >>> ep = t['My Name Is Earl'][1][22] + >>> ep + <Episode 01x22 - Stole a Badge> + >>> ep['episodename'] + u'Stole a Badge' + +Platform: UNKNOWN +Classifier: Intended Audience :: Developers +Classifier: Natural Language :: English +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Topic :: Multimedia +Classifier: Topic :: Utilities +Classifier: Topic :: Software Development :: Libraries :: Python Modules
View file
tvdb_api-1.5.tar.bz2/Rakefile -> tvdb_api-1.8.2.tar.gz/Rakefile
Changed
@@ -57,7 +57,7 @@ puts "Sending source-dist (sdist) to PyPi" if system("python setup.py sdist register upload") - print "tvdb_api uploaded!" + puts "tvdb_api uploaded!" end else @@ -81,7 +81,7 @@ task :test do puts "Nosetest'ing" - if not system("nosetests -v") + if not system("nosetests -v --with-doctest") raise "Test failed!" end
View file
tvdb_api-1.8.2.tar.gz/UNLICENSE
Added
@@ -0,0 +1,26 @@ +Copyright 2011-2012 Ben Dickson (dbr) + +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to <http://unlicense.org/>
View file
tvdb_api-1.5.tar.bz2/readme.md -> tvdb_api-1.8.2.tar.gz/readme.md
Changed
@@ -1,10 +1,10 @@ -# `tvdb_api` and `tvnamer` +# `tvdb_api` `tvdb_api` is an easy to use interface to [thetvdb.com][tvdb] -`tvnamer` has moved to a separate repository: [github.com/dbr/tvnamer][tvnamer] +`tvnamer` has moved to a separate repository: [github.com/dbr/tvnamer][tvnamer] - it is a utility which uses `tvdb_api` to rename files from `some.show.s01e03.blah.abc.avi` to `Some Show - [01x03] - The Episode Name.avi` (which works by getting the episode name from `tvdb_api`) -`tvnamer` is a utility which uses `tvdb_api` to rename files from `some.show.s01e03.blah.abc.avi` to `Some Show - [01x03] - The Episode Name.avi` (which works by getting the episode name from `tvdb_api`) +[![Build Status](https://secure.travis-ci.org/dbr/tvdb_api.png?branch=master)](http://travis-ci.org/dbr/tvdb_api) ## To install @@ -63,12 +63,12 @@ The data is stored in an attribute named `data`, within the Show instance: >>> t['scrubs'].data.keys() - ['networkid', 'rating', 'airs_dayofweek', 'contentrating', 'seriesname', 'id', 'airs_time', 'network', 'fanart', 'lastupdated', 'actors', 'overview', 'status', 'added', 'poster', 'imdb_id', 'genre', 'banner', 'seriesid', 'language', 'zap2it_id', 'addedby', 'firstaired', 'runtime'] + ['networkid', 'rating', 'airs_dayofweek', 'contentrating', 'seriesname', 'id', 'airs_time', 'network', 'fanart', 'lastupdated', 'actors', 'ratingcount', 'status', 'added', 'poster', 'imdb_id', 'genre', 'banner', 'seriesid', 'language', 'zap2it_id', 'addedby', 'firstaired', 'runtime', 'overview'] Although each element is also accessible via `t['scrubs']` for ease-of-use: >>> t['scrubs']['rating'] - u'9.1' + u'9.0' This is the recommended way of retrieving "one-off" data (for example, if you are only interested in "seriesname"). If you wish to iterate over all data, or check if a particular show has a specific piece of data, use the `data` attribute, @@ -103,7 +103,7 @@ Remember a simple list of actors is accessible via the default Show data: >>> t['scrubs']['actors'] - u'|Zach Braff|Donald Faison|Sarah Chalke|Christa Miller Lawrence|Aloma Wright|Robert Maschio|Sam Lloyd|Neil Flynn|Ken Jenkins|Judy Reyes|John C. McGinley|Travis Schuldt|Johnny Kastl|Heather Graham|Michael Mosley|Kerry Bish\xe9|Dave Franco|Eliza Coupe|' + u'|Zach Braff|Donald Faison|Sarah Chalke|Christa Miller|Aloma Wright|Robert Maschio|Sam Lloyd|Neil Flynn|Ken Jenkins|Judy Reyes|John C. McGinley|Travis Schuldt|Johnny Kastl|Heather Graham|Michael Mosley|Kerry Bish\xe9|Dave Franco|Eliza Coupe|' [tvdb]: http://www.thetvdb.com -[tvnamer]: http://github.com/dbr/tvnamer \ No newline at end of file +[tvnamer]: http://github.com/dbr/tvnamer
View file
tvdb_api-1.8.2.tar.gz/setup.cfg
Added
@@ -0,0 +1,5 @@ +[egg_info] +tag_build = +tag_date = 0 +tag_svn_revision = 0 +
View file
tvdb_api-1.5.tar.bz2/setup.py -> tvdb_api-1.8.2.tar.gz/setup.py
Changed
@@ -1,12 +1,12 @@ from setuptools import setup setup( name = 'tvdb_api', -version='1.5', +version='1.8.2', author='dbr/Ben', description='Interface to thetvdb.com', url='http://github.com/dbr/tvdb_api/tree/master', -license='GPLv2', +license='unlicense', long_description="""\ An easy to use API interface to TheTVDB.com @@ -21,11 +21,10 @@ u'Stole a Badge' """, -py_modules = ['tvdb_api', 'tvdb_ui', 'tvdb_exceptions', 'cache'], +py_modules = ['tvdb_api', 'tvdb_ui', 'tvdb_exceptions', 'tvdb_cache'], classifiers=[ "Intended Audience :: Developers", - "License :: OSI Approved :: GNU General Public License (GPL)", "Natural Language :: English", "Operating System :: OS Independent", "Programming Language :: Python",
View file
tvdb_api-1.5.tar.bz2/tests/runtests.py -> tvdb_api-1.8.2.tar.gz/tests/runtests.py
Changed
@@ -3,18 +3,15 @@ #author:dbr/Ben #project:tvdb_api #repository:http://github.com/dbr/tvdb_api -#license:Creative Commons GNU GPL v2 -# (http://creativecommons.org/licenses/GPL/2.0/) +#license:unlicense (http://unlicense.org/) import sys import unittest import test_tvdb_api -import test_tvnamer def main(): suite = unittest.TestSuite([ - unittest.TestLoader().loadTestsFromModule(test_tvnamer), unittest.TestLoader().loadTestsFromModule(test_tvdb_api) ])
View file
tvdb_api-1.5.tar.bz2/tests/test_tvdb_api.py -> tvdb_api-1.8.2.tar.gz/tests/test_tvdb_api.py
Changed
@@ -3,22 +3,23 @@ #author:dbr/Ben #project:tvdb_api #repository:http://github.com/dbr/tvdb_api -#license:Creative Commons GNU GPL v2 -# (http://creativecommons.org/licenses/GPL/2.0/) +#license:unlicense (http://unlicense.org/) """Unittests for tvdb_api """ +import os import sys import datetime import unittest -sys.path.append("..") +# Force parent directory onto path +sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) import tvdb_api import tvdb_ui -from tvdb_exceptions import (tvdb_error, tvdb_userabort, tvdb_shownotfound, - tvdb_seasonnotfound, tvdb_episodenotfound, tvdb_attributenotfound) +from tvdb_api import (tvdb_shownotfound, tvdb_seasonnotfound, +tvdb_episodenotfound, tvdb_attributenotfound) class test_tvdb_basic(unittest.TestCase): # Used to store the cached instance of Tvdb() @@ -76,6 +77,27 @@ True ) + def test_get_parent(self): + """Check accessing series from episode instance + """ + show = self.t['Battlestar Galactica (2003)'] + season = show[1] + episode = show[1][1] + + self.assertEquals( + season.show, + show + ) + + self.assertEquals( + episode.season, + season + ) + + self.assertEquals( + episode.season.show, + show + ) class test_tvdb_errors(unittest.TestCase): @@ -149,7 +171,7 @@ """Checks the searching of an entire show""" self.assertEquals( len(self.t['CNNNN'].search('CNNNN', key='episodename')), - 2 + 3 ) def test_aired_on(self): @@ -187,7 +209,7 @@ """ self.assertEquals( repr(self.t['CNNNN']), - "<Show Chaser Non-Stop News Network (CNNNN) (containing 2 seasons)>" + "<Show Chaser Non-Stop News Network (CNNNN) (containing 3 seasons)>" ) def test_repr_season(self): """Check repr() of Season @@ -405,7 +427,83 @@ """Check docstring examples works""" import doctest doctest.testmod(tvdb_api) -#end test_tvdb + + +class test_tvdb_custom_caching(unittest.TestCase): + def test_true_false_string(self): + """Tests setting cache to True/False/string + + Basic tests, only checking for errors + """ + + tvdb_api.Tvdb(cache = True) + tvdb_api.Tvdb(cache = False) + tvdb_api.Tvdb(cache = "/tmp") + + def test_invalid_cache_option(self): + """Tests setting cache to invalid value + """ + + try: + tvdb_api.Tvdb(cache = 2.3) + except ValueError: + pass + else: + self.fail("Expected ValueError from setting cache to float") + + def test_custom_urlopener(self): + class UsedCustomOpener(Exception): + pass + + import urllib2 + class TestOpener(urllib2.BaseHandler): + def default_open(self, request): + print request.get_method() + raise UsedCustomOpener("Something") + + custom_opener = urllib2.build_opener(TestOpener()) + t = tvdb_api.Tvdb(cache = custom_opener) + try: + t['scrubs'] + except UsedCustomOpener: + pass + else: + self.fail("Did not use custom opener") + +class test_tvdb_by_id(unittest.TestCase): + t = None + def setUp(self): + if self.t is None: + self.__class__.t = tvdb_api.Tvdb(cache = True, actors = True) + + def test_actors_is_correct_datatype(self): + """Check show/_actors key exists and is correct type""" + self.assertEquals( + self.t[76156]['seriesname'], + 'Scrubs' + ) + + +class test_tvdb_zip(unittest.TestCase): + # Used to store the cached instance of Tvdb() + t = None + + def setUp(self): + if self.t is None: + self.__class__.t = tvdb_api.Tvdb(cache = True, useZip = True) + + def test_get_series_from_zip(self): + """ + """ + self.assertEquals(self.t['scrubs'][1][4]['episodename'], 'My Old Lady') + self.assertEquals(self.t['sCruBs']['seriesname'], 'Scrubs') + + def test_spaces_from_zip(self): + """Checks shownames with spaces + """ + self.assertEquals(self.t['My Name Is Earl']['seriesname'], 'My Name Is Earl') + self.assertEquals(self.t['My Name Is Earl'][1][4]['episodename'], 'Faked His Own Death') + if __name__ == '__main__': runner = unittest.TextTestRunner(verbosity = 2)
View file
tvdb_api-1.8.2.tar.gz/tvdb_api.egg-info
Added
+(directory)
View file
tvdb_api-1.8.2.tar.gz/tvdb_api.egg-info/PKG-INFO
Added
@@ -0,0 +1,27 @@ +Metadata-Version: 1.0 +Name: tvdb-api +Version: 1.8.2 +Summary: Interface to thetvdb.com +Home-page: http://github.com/dbr/tvdb_api/tree/master +Author: dbr/Ben +Author-email: UNKNOWN +License: unlicense +Description: An easy to use API interface to TheTVDB.com + Basic usage is: + + >>> import tvdb_api + >>> t = tvdb_api.Tvdb() + >>> ep = t['My Name Is Earl'][1][22] + >>> ep + <Episode 01x22 - Stole a Badge> + >>> ep['episodename'] + u'Stole a Badge' + +Platform: UNKNOWN +Classifier: Intended Audience :: Developers +Classifier: Natural Language :: English +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Topic :: Multimedia +Classifier: Topic :: Utilities +Classifier: Topic :: Software Development :: Libraries :: Python Modules
View file
tvdb_api-1.8.2.tar.gz/tvdb_api.egg-info/SOURCES.txt
Added
@@ -0,0 +1,16 @@ +MANIFEST.in +Rakefile +UNLICENSE +readme.md +setup.py +tvdb_api.py +tvdb_cache.py +tvdb_exceptions.py +tvdb_ui.py +tests/gprof2dot.py +tests/runtests.py +tests/test_tvdb_api.py +tvdb_api.egg-info/PKG-INFO +tvdb_api.egg-info/SOURCES.txt +tvdb_api.egg-info/dependency_links.txt +tvdb_api.egg-info/top_level.txt \ No newline at end of file
View file
tvdb_api-1.8.2.tar.gz/tvdb_api.egg-info/dependency_links.txt
Added
@@ -0,0 +1,1 @@ +
View file
tvdb_api-1.8.2.tar.gz/tvdb_api.egg-info/top_level.txt
Added
@@ -0,0 +1,4 @@ +tvdb_api +tvdb_ui +tvdb_exceptions +tvdb_cache
View file
tvdb_api-1.5.tar.bz2/tvdb_api.py -> tvdb_api-1.8.2.tar.gz/tvdb_api.py
Changed
@@ -3,8 +3,7 @@ #author:dbr/Ben #project:tvdb_api #repository:http://github.com/dbr/tvdb_api -#license:Creative Commons GNU GPL v2 -# (http://creativecommons.org/licenses/GPL/2.0/) +#license:unlicense (http://unlicense.org/) """Simple-to-use Python interface to The TVDB's API (www.thetvdb.com) @@ -16,17 +15,19 @@ u'Cabin Fever' """ __author__ = "dbr/Ben" -__version__ = "1.5" +__version__ = "1.8.2" import os -import sys +import time import urllib import urllib2 +import getpass import StringIO import tempfile import warnings import logging import datetime +import zipfile try: import xml.etree.cElementTree as ElementTree @@ -39,7 +40,7 @@ gzip = None -from cache import CacheHandler +from tvdb_cache import CacheHandler from tvdb_ui import BaseUI, ConsoleUI from tvdb_exceptions import (tvdb_error, tvdb_userabort, tvdb_shownotfound, @@ -54,7 +55,27 @@ class ShowContainer(dict): """Simple dict that holds a series of Show instances """ - pass + + def __init__(self): + self._stack = [] + self._lastgc = time.time() + + def __setitem__(self, key, value): + self._stack.append(key) + + #keep only the 100th latest results + if time.time() - self._lastgc > 20: + tbd = self._stack[:-100] + i = 0 + for o in tbd: + del self[o] + del self._stack[i] + i += 1 + + _lastgc = time.time() + del tbd + + super(ShowContainer, self).__setitem__(key, value) class Show(dict): @@ -155,6 +176,11 @@ class Season(dict): + def __init__(self, show = None): + """The show attribute points to the parent show + """ + self.show = show + def __repr__(self): return "<Season instance (containing %s episodes)>" % ( len(self.keys()) @@ -188,6 +214,11 @@ class Episode(dict): + def __init__(self, season = None): + """The season attribute points to the parent season + """ + self.season = season + def __repr__(self): seasno = int(self.get(u'seasonnumber', 0)) epno = int(self.get(u'episodenumber', 0)) @@ -276,7 +307,9 @@ language = None, search_all_languages = False, apikey = None, - forceConnect=False): + forceConnect=False, + useZip=False): + """interactive (True/False): When True, uses built-in console UI is used to select the correct show. When False, the first search result is used. @@ -292,10 +325,13 @@ >>> import logging >>> logging.basicConfig(level = logging.DEBUG) - cache (True/False/str/unicode): - Retrieved XML are persisted to to disc. If true, stores in tvdb_api - folder under your systems TEMP_DIR, if set to str/unicode instance it - will use this as the cache location. If False, disables caching. + cache (True/False/str/unicode/urllib2 opener): + Retrieved XML are persisted to to disc. If true, stores in + tvdb_api folder under your systems TEMP_DIR, if set to + str/unicode instance it will use this as the cache + location. If False, disables caching. Can also be passed + an arbitrary Python object, which is used as a urllib2 + opener, which should be created by urllib2.build_opener banners (True/False): Retrieves the banners for a show. These are accessed @@ -338,7 +374,12 @@ If true it will always try to connect to theTVDB.com even if we recently timed out. By default it will wait one minute before trying again, and any requests within that one minute window will - return an exception immediately. + return an exception immediately. + + useZip (bool): + Download the zip archive where possibale, instead of the xml. + This is only used when all episodes are pulled. + And only the main language xml is used, the actor and banner xml are lost. """ global lastTimeout @@ -367,21 +408,35 @@ self.config['search_all_languages'] = search_all_languages + self.config['useZip'] = useZip + + if cache is True: self.config['cache_enabled'] = True self.config['cache_location'] = self._getTempDir() + self.urlopener = urllib2.build_opener( + CacheHandler(self.config['cache_location']) + ) + + elif cache is False: + self.config['cache_enabled'] = False + self.urlopener = urllib2.build_opener() # default opener with no caching + elif isinstance(cache, basestring): self.config['cache_enabled'] = True self.config['cache_location'] = cache - else: - self.config['cache_enabled'] = False - - if self.config['cache_enabled']: self.urlopener = urllib2.build_opener( CacheHandler(self.config['cache_location']) ) + + elif isinstance(cache, urllib2.OpenerDirector): + # If passed something from urllib2.build_opener, use that + log().debug("Using %r as urlopener" % cache) + self.config['cache_enabled'] = True + self.urlopener = cache + else: - self.urlopener = urllib2.build_opener() + raise ValueError("Invalid value for Cache %r (type was %s)" % (cache, type(cache))) self.config['banners_enabled'] = banners self.config['actors_enabled'] = actors @@ -411,7 +466,7 @@ 'hu': 19, 'ja': 25, 'he': 24, 'ko': 32, 'sv': 8, 'sl': 30} if language is None: - self.config['language'] = None + self.config['language'] = 'en' else: if language not in self.config['valid_languages']: raise ValueError("Invalid language %s, options are: %s" % ( @@ -430,6 +485,7 @@ self.config['url_getSeries'] = u"%(base_url)s/api/GetSeries.php?seriesname=%%s&language=%(language)s" % self.config self.config['url_epInfo'] = u"%(base_url)s/api/%(apikey)s/series/%%s/all/%%s.xml" % self.config + self.config['url_epInfo_zip'] = u"%(base_url)s/api/%(apikey)s/series/%%s/all/%%s.zip" % self.config self.config['url_seriesInfo'] = u"%(base_url)s/api/%(apikey)s/series/%%s/%%s.xml" % self.config self.config['url_actorsInfo'] = u"%(base_url)s/api/%(apikey)s/series/%%s/actors.xml" % self.config @@ -440,11 +496,18 @@ #end __init__ def _getTempDir(self): - """Returns the [system temp dir]/tvdb_api + """Returns the [system temp dir]/tvdb_api-u501 (or + tvdb_api-myuser) """ - return os.path.join(tempfile.gettempdir(), "tvdb_api") + if hasattr(os, 'getuid'): + uid = "u%d" % (os.getuid()) + else: + # For Windows + uid = getpass.getuser() - def _loadUrl(self, url, recache = False): + return os.path.join(tempfile.gettempdir(), "tvdb_api-%s" % (uid)) + + def _loadUrl(self, url, recache = False, language=None): global lastTimeout try: log().debug("Retrieving URL %s" % url) @@ -470,21 +533,36 @@ stream = StringIO.StringIO(resp.read()) gz = gzip.GzipFile(fileobj=stream) return gz.read() - + raise tvdb_error("Received gzip data from thetvdb.com, but could not correctly handle it") - + + if 'application/zip' in resp.headers.get("Content-Type", ''): + try: + # TODO: The zip contains actors.xml and banners.xml, which are currently ignored [GH-20] + log().debug("We recived a zip file unpacking now ...") + zipdata = StringIO.StringIO() + zipdata.write(resp.read()) + myzipfile = zipfile.ZipFile(zipdata) + return myzipfile.read('%s.xml' % language) + except zipfile.BadZipfile: + if 'x-local-cache' in resp.headers: + resp.delete_cache() + raise tvdb_error("Bad zip file received from thetvdb.com, could not read it") + return resp.read() - def _getetsrc(self, url): + def _getetsrc(self, url, language=None): """Loads a URL using caching, returns an ElementTree of the source """ - src = self._loadUrl(url) + src = self._loadUrl(url, language=language) try: - return ElementTree.fromstring(src) + # TVDB doesn't sanitize \r (CR) from user input in some fields, + # remove it to avoid errors. Change from SickBeard, from will14m + return ElementTree.fromstring(src.rstrip("\r")) except SyntaxError: - src = self._loadUrl(url, recache=True) + src = self._loadUrl(url, recache=True, language=language) try: - return ElementTree.fromstring(src) + return ElementTree.fromstring(src.rstrip("\r")) except SyntaxError, exceptionmsg: errormsg = "There was an error with the XML retrieved from thetvdb.com:\n%s" % ( exceptionmsg @@ -518,9 +596,9 @@ if sid not in self.shows: self.shows[sid] = Show() if seas not in self.shows[sid]: - self.shows[sid][seas] = Season() + self.shows[sid][seas] = Season(show = self.shows[sid]) if ep not in self.shows[sid][seas]: - self.shows[sid][seas][ep] = Episode() + self.shows[sid][seas][ep] = Episode(season = self.shows[sid][seas]) self.shows[sid][seas][ep][attrib] = value #end _set_item @@ -684,6 +762,8 @@ if self.config['language'] is None: log().debug('Config language is none, using show language') + if language is None: + raise tvdb_error("config['language'] was None, this should not happen") getShowInLanguage = language else: log().debug( @@ -722,7 +802,13 @@ # Parse episode data log().debug('Getting all episodes of %s' % (sid)) - epsEt = self._getetsrc( self.config['url_epInfo'] % (sid, language) ) + + if self.config['useZip']: + url = self.config['url_epInfo_zip'] % (sid, language) + else: + url = self.config['url_epInfo'] % (sid, language) + + epsEt = self._getetsrc( url, language=language) for cur_ep in epsEt.findall("Episode"): seas_no = int(cur_ep.find('SeasonNumber').text)
View file
tvdb_api-1.8.2.tar.gz/tvdb_cache.py
Added
@@ -0,0 +1,252 @@ +#!/usr/bin/env python +#encoding:utf-8 +#author:dbr/Ben +#project:tvdb_api +#repository:http://github.com/dbr/tvdb_api +#license:unlicense (http://unlicense.org/) + +""" +urllib2 caching handler +Modified from http://code.activestate.com/recipes/491261/ +""" +from __future__ import with_statement + +__author__ = "dbr/Ben" +__version__ = "1.8.2" + +import os +import time +import errno +import httplib +import urllib2 +import StringIO +from hashlib import md5 +from threading import RLock + +cache_lock = RLock() + +def locked_function(origfunc): + """Decorator to execute function under lock""" + def wrapped(*args, **kwargs): + cache_lock.acquire() + try: + return origfunc(*args, **kwargs) + finally: + cache_lock.release() + return wrapped + +def calculate_cache_path(cache_location, url): + """Checks if [cache_location]/[hash_of_url].headers and .body exist + """ + thumb = md5(url).hexdigest() + header = os.path.join(cache_location, thumb + ".headers") + body = os.path.join(cache_location, thumb + ".body") + return header, body + +def check_cache_time(path, max_age): + """Checks if a file has been created/modified in the [last max_age] seconds. + False means the file is too old (or doesn't exist), True means it is + up-to-date and valid""" + if not os.path.isfile(path): + return False + cache_modified_time = os.stat(path).st_mtime + time_now = time.time() + if cache_modified_time < time_now - max_age: + # Cache is old + return False + else: + return True + +@locked_function +def exists_in_cache(cache_location, url, max_age): + """Returns if header AND body cache file exist (and are up-to-date)""" + hpath, bpath = calculate_cache_path(cache_location, url) + if os.path.exists(hpath) and os.path.exists(bpath): + return( + check_cache_time(hpath, max_age) + and check_cache_time(bpath, max_age) + ) + else: + # File does not exist + return False + +@locked_function +def store_in_cache(cache_location, url, response): + """Tries to store response in cache.""" + hpath, bpath = calculate_cache_path(cache_location, url) + try: + outf = open(hpath, "wb") + headers = str(response.info()) + outf.write(headers) + outf.close() + + outf = open(bpath, "wb") + outf.write(response.read()) + outf.close() + except IOError: + return True + else: + return False + +@locked_function +def delete_from_cache(cache_location, url): + """Deletes a response in cache.""" + hpath, bpath = calculate_cache_path(cache_location, url) + try: + if os.path.exists(hpath): + os.remove(hpath) + if os.path.exists(bpath): + os.remove(bpath) + except IOError: + return True + else: + return False + +class CacheHandler(urllib2.BaseHandler): + """Stores responses in a persistant on-disk cache. + + If a subsequent GET request is made for the same URL, the stored + response is returned, saving time, resources and bandwidth + """ + @locked_function + def __init__(self, cache_location, max_age = 21600): + """The location of the cache directory""" + self.max_age = max_age + self.cache_location = cache_location + if not os.path.exists(self.cache_location): + try: + os.mkdir(self.cache_location) + except OSError, e: + if e.errno == errno.EEXIST and os.path.isdir(self.cache_location): + # File exists, and it's a directory, + # another process beat us to creating this dir, that's OK. + pass + else: + # Our target dir is already a file, or different error, + # relay the error! + raise + + def default_open(self, request): + """Handles GET requests, if the response is cached it returns it + """ + if request.get_method() is not "GET": + return None # let the next handler try to handle the request + + if exists_in_cache( + self.cache_location, request.get_full_url(), self.max_age + ): + return CachedResponse( + self.cache_location, + request.get_full_url(), + set_cache_header = True + ) + else: + return None + + def http_response(self, request, response): + """Gets a HTTP response, if it was a GET request and the status code + starts with 2 (200 OK etc) it caches it and returns a CachedResponse + """ + if (request.get_method() == "GET" + and str(response.code).startswith("2") + ): + if 'x-local-cache' not in response.info(): + # Response is not cached + set_cache_header = store_in_cache( + self.cache_location, + request.get_full_url(), + response + ) + else: + set_cache_header = True + #end if x-cache in response + + return CachedResponse( + self.cache_location, + request.get_full_url(), + set_cache_header = set_cache_header + ) + else: + return response + +class CachedResponse(StringIO.StringIO): + """An urllib2.response-like object for cached responses. + + To determine if a response is cached or coming directly from + the network, check the x-local-cache header rather than the object type. + """ + + @locked_function + def __init__(self, cache_location, url, set_cache_header=True): + self.cache_location = cache_location + hpath, bpath = calculate_cache_path(cache_location, url) + + StringIO.StringIO.__init__(self, file(bpath, "rb").read()) + + self.url = url + self.code = 200 + self.msg = "OK" + headerbuf = file(hpath, "rb").read() + if set_cache_header: + headerbuf += "x-local-cache: %s\r\n" % (bpath) + self.headers = httplib.HTTPMessage(StringIO.StringIO(headerbuf)) + + def info(self): + """Returns headers + """ + return self.headers + + def geturl(self): + """Returns original URL + """ + return self.url + + @locked_function + def recache(self): + new_request = urllib2.urlopen(self.url) + set_cache_header = store_in_cache( + self.cache_location, + new_request.url, + new_request + ) + CachedResponse.__init__(self, self.cache_location, self.url, True) + + @locked_function + def delete_cache(self): + delete_from_cache( + self.cache_location, + self.url + ) + + +if __name__ == "__main__": + def main(): + """Quick test/example of CacheHandler""" + opener = urllib2.build_opener(CacheHandler("/tmp/")) + response = opener.open("http://google.com") + print response.headers + print "Response:", response.read() + + response.recache() + print response.headers + print "After recache:", response.read() + + # Test usage in threads + from threading import Thread + class CacheThreadTest(Thread): + lastdata = None + def run(self): + req = opener.open("http://google.com") + newdata = req.read() + if self.lastdata is None: + self.lastdata = newdata + assert self.lastdata == newdata, "Data was not consistent, uhoh" + req.recache() + threads = [CacheThreadTest() for x in range(50)] + print "Starting threads" + [t.start() for t in threads] + print "..done" + print "Joining threads" + [t.join() for t in threads] + print "..done" + main()
View file
tvdb_api-1.5.tar.bz2/tvdb_exceptions.py -> tvdb_api-1.8.2.tar.gz/tvdb_exceptions.py
Changed
@@ -3,14 +3,13 @@ #author:dbr/Ben #project:tvdb_api #repository:http://github.com/dbr/tvdb_api -#license:Creative Commons GNU GPL v2 -# (http://creativecommons.org/licenses/GPL/2.0/) +#license:unlicense (http://unlicense.org/) """Custom exceptions used or raised by tvdb_api """ __author__ = "dbr/Ben" -__version__ = "1.5" +__version__ = "1.8.2" __all__ = ["tvdb_error", "tvdb_userabort", "tvdb_shownotfound", "tvdb_seasonnotfound", "tvdb_episodenotfound", "tvdb_attributenotfound"]
View file
tvdb_api-1.5.tar.bz2/tvdb_ui.py -> tvdb_api-1.8.2.tar.gz/tvdb_ui.py
Changed
@@ -3,8 +3,7 @@ #author:dbr/Ben #project:tvdb_api #repository:http://github.com/dbr/tvdb_api -#license:Creative Commons GNU GPL v2 -# (http://creativecommons.org/licenses/GPL/2.0/) +#license:unlicense (http://unlicense.org/) """Contains included user interfaces for Tvdb show selection. @@ -44,7 +43,7 @@ """ __author__ = "dbr/Ben" -__version__ = "1.5" +__version__ = "1.8.2" import logging import warnings @@ -73,19 +72,30 @@ """Interactively allows the user to select a show from a console based UI """ - def _displaySeries(self, allSeries): + def _displaySeries(self, allSeries, limit = 6): """Helper function, lists series with corresponding ID """ + if limit is not None: + toshow = allSeries[:limit] + else: + toshow = allSeries + print "TVDB Search Results:" - for i, cshow in enumerate(allSeries[:6]): + for i, cshow in enumerate(toshow): i_show = i + 1 # Start at more human readable number 1 (not 0) log().debug('Showing allSeries[%s], series %s)' % (i_show, allSeries[i]['seriesname'])) - print "%s -> %s [%s] # http://thetvdb.com/?tab=series&id=%s&lid=%s" % ( + if i == 0: + extra = " (default)" + else: + extra = "" + + print "%s -> %s [%s] # http://thetvdb.com/?tab=series&id=%s&lid=%s%s" % ( i_show, cshow['seriesname'].encode("UTF-8", "ignore"), cshow['language'].encode("UTF-8", "ignore"), str(cshow['id']), - cshow['lid'] + cshow['lid'], + extra ) def selectSeries(self, allSeries): @@ -102,7 +112,7 @@ while True: # return breaks this loop try: - print "Enter choice (first number, ? for help):" + print "Enter choice (first number, return for default, 'all', ? for help):" ans = raw_input() except KeyboardInterrupt: raise tvdb_userabort("User aborted (^c keyboard interupt)") @@ -113,20 +123,29 @@ try: selected_id = int(ans) - 1 # The human entered 1 as first result, not zero except ValueError: # Input was not number + if len(ans.strip()) == 0: + # Default option + log().debug('Default option, returning first series') + return allSeries[0] if ans == "q": log().debug('Got quit command (q)') raise tvdb_userabort("User aborted ('q' quit command)") elif ans == "?": print "## Help" print "# Enter the number that corresponds to the correct show." + print "# a - display all results" + print "# all - display all results" print "# ? - this help" print "# q - abort tvnamer" + print "# Press return with no input to select first result" + elif ans.lower() in ["a", "all"]: + self._displaySeries(allSeries, limit = None) else: log().debug('Unknown keypress %s' % (ans)) else: log().debug('Trying to return ID: %d' % (selected_id)) try: - return allSeries[ selected_id ] + return allSeries[selected_id] except IndexError: log().debug('Invalid show number entered!') print "Invalid number (%s) selected!"
Locations
Projects
Search
Status Monitor
Help
Open Build Service
OBS Manuals
API Documentation
OBS Portal
Reporting a Bug
Contact
Mailing List
Forums
Chat (IRC)
Twitter
Open Build Service (OBS)
is an
openSUSE project
.