The `Season` object is a python dict with additional properties::
>>> s4 = doctor_who.season(4)
>>> s4.is_current
False
>>> s4.premiere
Doctor Who (2005) 4x01 Partners in Crime
>>> s4.finale
Doctor Who (2005) 4x13 Journey's End (3)
>>> s4.episode(3)
Doctor Who (2005) 4x03 Planet of the Ood
>>> s4.values()
[Doctor Who (2005) 4x01 Partners in Crime, Doctor Who (2005) 4x02 The Fires of... ]
>>> s4.keys()
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]
The `Episode` object contains all information related to an certain episode::
>>> rose = doctor_who.season(1).episode(1)
>>> rose.airdate
datetime.date(2005, 3, 26)
>>> rose.link
'http://www.tvrage.com/DoctorWho_2005/episodes/52117'
>>> rose.number
1
>>> rose.prodnumber
'101'
>>> rose.season
1
>>> rose.show
'Doctor Who (2005)'
>>> rose.title
'Rose'
For some episodes there is detailed summary information available. This information is not provided by the XML feeds, so it has to be extracted from the episode's overview page via web scraping. Since it would be quite slow to load all those web pages for entire seasons upfront, the summary information is only loaded when the `Episode.summary` property is actually beeing read::
>>> nextff = tvrage.api.Show('flashforward').next_episode
>>> nextff
FlashForward 1x18 Goodbye Yellow Brick Road
>>> nextff.summary # spoilers alert!... you have to try this one for your self ;)
Using the Quickinfo Feed
++++++++++++++++++++++++
The modul `tvrage.quickinfo` provides easy access to the tvrage's `quickinfo feed`_.
.. _quickinfo feed: http://services.tvrage.com/info.php?page=quickinfo
You can fetch meta data about a tv show alone::
>>> from tvrage import quickinfo
>>> quickinfo.fetch('doctor who 2005')
{'Status': 'Returning Series', 'Genres': ['Action', 'Adventure', 'Sci-Fi'], 'Network': 'BBC One (United Kingdom)',
'Classification': 'Scripted', 'Started': 'Mar/26/2005', 'Show Name': 'Doctor Who (2005)', 'Show URL':
'http://www.tvrage.com/DoctorWho_2005', 'Premiered': '2005', 'Airtime': 'Saturday at 07:00 pm', 'Ended': '',
'Show ID': '3332', 'Country': 'United Kingdom', 'Runtime': '50', 'Latest Episode': ['05x13', 'The Big Bang (2)', 'Jun/26/2010']}
or you can fetch informations about an specific episode combined with the show's meta data::
>>> epinfo = quickinfo.fetch('doctor who 2005', ep='1x01')
>>> epinfo
{'Status': 'Returning Series', 'Genres': ['Action', 'Adventure', 'Sci-Fi'], ...
>>> epinfo['Episode Info']
['01x01', 'Rose', '26/Mar/2005']
>>> epinfo['Episode URL']
'http://www.tvrage.com/DoctorWho_2005/episodes/52117'
ckreutzer-python-tvrage-c8e9781/requirements.txt 0000664 0000000 0000000 00000000025 12025677021 0022172 0 ustar 00root root 0000000 0000000 BeautifulSoup==3.2.1
ckreutzer-python-tvrage-c8e9781/setup.py 0000664 0000000 0000000 00000001716 12025677021 0020430 0 ustar 00root root 0000000 0000000 #!/usr/bin/env python
import os
from distutils.core import setup
from tvrage import __version__, __author__, __license__
setup(name='python-tvrage',
description='python client for the tvrage.com XML API',
long_description = file(
os.path.join(os.path.dirname(__file__),'README.rst')).read(),
license=__license__,
version=__version__,
author=__author__,
author_email='herr.kreutzer@gmail.com',
# url='http://bitbucket.org/ckreutzer/python-tvrage/',
url='https://github.com/ckreutzer/python-tvrage',
packages=['tvrage'],
install_requires = ["BeautifulSoup"],
classifiers = [
'Development Status :: 4 - Beta',
'Intended Audience :: Developers',
'License :: OSI Approved :: BSD License',
'Topic :: Software Development :: Libraries :: Python Modules',
'Programming Language :: Python',
'Operating System :: OS Independent'
]
)
ckreutzer-python-tvrage-c8e9781/tests/ 0000775 0000000 0000000 00000000000 12025677021 0020053 5 ustar 00root root 0000000 0000000 ckreutzer-python-tvrage-c8e9781/tests/api_tests.py 0000664 0000000 0000000 00000015442 12025677021 0022426 0 ustar 00root root 0000000 0000000 #!/usr/bin/env python
# Copyright (c) 2009, Christian Kreutzer
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions, and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions, and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the author of this software nor the name of
# contributors to this software may be used to endorse or promote products
# derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
import unittest
from datetime import date
from tvrage.api import Show
from tvrage.exceptions import (ShowHasEnded, FinaleMayNotBeAnnouncedYet,
ShowNotFound)
class ShowTest(unittest.TestCase):
show = Show('Person of Interest')
dead_show = Show('house m.d.')
def test_show_still_running(self):
assert self.show.ended == 0
def test_get_season(self):
season = self.show.season(1)
assert len(season) == 23
assert season.episode(1).title == 'Pilot'
def test_get_next_episode_from_dead_show(self):
try:
self.dead_show.next_episode
except Exception, e:
assert isinstance(e, ShowHasEnded)
# this test may break during off seasons...
# def testGetNextEpisode(self):
# today = date.today()
# airdate = self.show.next_episode.airdate
# assert airdate >= today
def test_get_pilot(self):
p = self.show.pilot
assert p.season == 1
assert p.number == 1
assert p.title == 'Pilot'
def test_get_current_season_from_dead_show(self):
try:
self.dead_show.current_season
except Exception, e:
assert isinstance(e, ShowHasEnded)
# this test may break when new season listings are posted
#def testGetCurrentSeason(self):
# assert self.show.current_season.premiere.season == 6
def test_get_upcoming_eps(self):
today = date.today()
for ep in self.show.upcoming_episodes:
airdate = ep.airdate
assert airdate >= today
def test_get_latest_ep(self):
today = date.today()
ep = Show('FlashForward').latest_episode
assert ep.airdate <= today
assert ep.title == 'Future Shock'
def test_non_existant_show_raises_proper_exception(self):
try:
Show('yaddayadda')
except Exception, e:
assert isinstance(e, ShowNotFound)
assert e.value == 'yaddayadda'
def test_synopsis(self):
assert self.dead_show.synopsis.startswith(
u"As an infectious disease specialist,Dr. Gregory House"
"(Hugh Laurie) is a brilliant diagnostician who loves the "
"challenges of the medical puzzles he must solve in order to save"
" lives.")
def test_show_with_missing_seasons_doesnt_mess_up_season_count(self):
# Seasons 39 - 47 are missing
s = Show(u'House Hunters')
assert s.seasons >= 48
class SeasonTest(unittest.TestCase):
season = Show('house m.d.').season(3)
def test_get_episode(self):
assert self.season.episode(1).number == 1
assert self.season.episode(6).number == 6
assert self.season.episode(24).number == 24
def test_get_premiere(self):
ep = self.season.premiere
assert ep.number == 1
def test_get_finale(self):
assert self.season.finale.number == 24
# and now the execption:
# NOTE: this test may break, when the season finale actually gets
# announced
try:
Show('Doctor Who 2005').current_season.finale
except Exception, e:
assert isinstance(e, FinaleMayNotBeAnnouncedYet)
class EpisodeTest(unittest.TestCase):
ep = Show('house m.d.').season(3).episode(6)
def test_show(self):
assert self.ep.show == 'House'
def test_season(self):
assert self.ep.season == 3
def test_num(self):
assert self.ep.number == 6
def test_airdate(self):
assert self.ep.airdate == date(2006, 11, 07)
def test_title(self):
assert self.ep.title == 'Que Sera Sera'
def test_link(self):
assert self.ep.link == \
'http://www.tvrage.com/House/episodes/461013'
def test_summary_old(self):
s = "An immensely overweight man is brought in after he's found at"\
" home in a coma. Upon regaining consciousness, he demands"\
" to be released. When Cameron comes up with a way to force"\
" him to stay, the man insists the find a reason for his"\
" illness other than his obesity. Meanwhile, Det. Tritter"\
" arrests House, searches his home, and questions his"\
" co-workers about his Vicodin usage."
assert self.ep.summary == s
def test_summary_new(self):
ep = Show('chaos').season(1).episode(8)
s = 'The agents go against orders to capture an arms dealer, but'\
' their actions trouble Rick who must decide whether to report'\
' their unauthorized activities to the CIA director.'
assert ep.summary == s
def test_recap_url(self):
ep = Show('house m.d.').season(1).episode(1)
s = 'http://www.tvrage.com/House/episodes/84699/recap'
assert ep.recap_url == s
def test_id(self):
assert self.ep.id == '461013'
def test_recap(self):
ep = Show('house m.d.').season(1).episode(1)
s = 'House explains that when someone eats poorly prepared pork,'\
' tapeworms lodge in the bowels.'
assert s in ep.recap
def test_recap_negativ(self):
ep = Show('house m.d.').season(3).episode(6)
assert ep.recap == 'No recap available'
if __name__ == '__main__':
unittest.main()
ckreutzer-python-tvrage-c8e9781/tests/quickinfo_tests.py 0000664 0000000 0000000 00000006304 12025677021 0023642 0 ustar 00root root 0000000 0000000 #!/usr/bin/env python
# Copyright (c) 2009, Christian Kreutzer
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions, and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions, and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the author of this software nor the name of
# contributors to this software may be used to endorse or promote products
# derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
import unittest
from tvrage import quickinfo
from tvrage.exceptions import ShowNotFound
class QuickInfoTest(unittest.TestCase):
def test_showinfo(self):
show = quickinfo.fetch('Doctor Who 2005')
assert show['Show ID'] == '3332'
assert show['Show Name'] == 'Doctor Who (2005)'
assert show['Show URL'] == 'http://www.tvrage.com/DoctorWho_2005'
assert show['Premiered'] == '2005'
assert show['Started'] == 'Mar/26/2005'
assert show['Ended'] == ''
# Note: this test may break around X-mas 2010
#assert show['Latest Episode'] == \
# ['05x13', 'The Big Bang (2)', 'Jun/26/2010']
assert show['Country'] == 'United Kingdom'
# hope the next one never breaks ;-)
assert show['Status'] == 'Returning Series'
assert show['Classification'] == 'Scripted'
assert show['Genres'] == ['Action', 'Adventure', 'Sci-Fi']
assert show['Network'] == 'BBC One (United Kingdom)'
assert show['Airtime'] == 'Saturday at 07:35 pm' # this may break
assert show['Runtime'] == '60'
def test_epinfo(self):
show_ep = quickinfo.fetch('Doctor Who 2005', ep='1x01')
assert show_ep['Episode Info'] == ['01x01', 'Rose', '26/Mar/2005']
assert show_ep['Episode URL'] == \
'http://www.tvrage.com/DoctorWho_2005/episodes/52117'
def test_non_existant_show_raises_proper_exception(self):
try:
quickinfo.fetch('yaddayadda')
except Exception, e:
assert isinstance(e, ShowNotFound)
assert e.value == 'yaddayadda'
if __name__ == '__main__':
unittest.main()
ckreutzer-python-tvrage-c8e9781/tvrage/ 0000775 0000000 0000000 00000000000 12025677021 0020201 5 ustar 00root root 0000000 0000000 ckreutzer-python-tvrage-c8e9781/tvrage/__init__.py 0000664 0000000 0000000 00000000115 12025677021 0022307 0 ustar 00root root 0000000 0000000
__version__ = '0.4.1'
__author__ = 'Christian Kreutzer'
__license__ = 'BSD'
ckreutzer-python-tvrage-c8e9781/tvrage/api.py 0000664 0000000 0000000 00000020464 12025677021 0021332 0 ustar 00root root 0000000 0000000 # Copyright (c) 2009, Christian Kreutzer
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions, and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions, and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the author of this software nor the name of
# contributors to this software may be used to endorse or promote products
# derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
import feeds
from datetime import date
from time import mktime, strptime
from exceptions import (ShowHasEnded, FinaleMayNotBeAnnouncedYet,
ShowNotFound, NoNewEpisodesAnnounced)
from util import _fetch, parse_synopsis
class Episode(object):
"""represents an tv episode description from tvrage.com"""
def __init__(self, show, season, airdate, title, link, number, prodnumber):
self.show = show
self.season = season
try:
self.airdate = date.fromtimestamp(mktime(
strptime(airdate, '%Y-%m-%d')))
except ValueError:
self.airdate = None
self.title = title
self.link = link
self.number = number
self.prodnumber = prodnumber
self.recap_url = link + '/recap'
self.id = link.split('/')[-1]
def __unicode__(self):
return u'%s %sx%02d %s' % (self.show,
self.season, self.number, self.title)
__str__ = __repr__ = __unicode__
@property
def summary(self):
"""parses the episode's summary from the episode's tvrage page"""
try:
page = _fetch(self.link).read()
if not 'Click here to add a summary' in page:
summary = parse_synopsis(page, cleanup='var addthis_config')
return summary
except Exception, e:
print('Episode.summary: %s, %s' % (self, e))
return 'No summary available'
@property
def recap(self):
"""parses the episode's recap text from the episode's tvrage recap
page"""
try:
page = _fetch(self.recap_url).read()
if not 'Click here to add a recap for' in page:
recap = parse_synopsis(page,
cleanup='Share this article with your'
' friends')
return recap
except Exception, e:
print('Episode.recap:urlopen: %s, %s' % (self, e))
return 'No recap available'
class Season(dict):
"""represents a season container object"""
is_current = False
def episode(self, n):
"""returns the nth episode"""
return self[n]
@property
def premiere(self):
"""returns the season premiere episode"""
return self[1] # analog to the real world, season is 1-based
@property
def finale(self):
"""returns the season finale episode"""
if not self.is_current:
return self[len(self.keys())]
else:
raise FinaleMayNotBeAnnouncedYet('this is the current season...')
class Show(object):
"""represents a TV show description from tvrage.com
this class is kind of a wrapper around the following of tvrage's xml feeds:
* http://www.tvrage.com/feeds/search.php?show=SHOWNAME
* http://www.tvrage.com/feeds/episode_list.php?sid=SHOWID
"""
def __init__(self, name):
self.shortname = name
self.episodes = {}
# the following properties will be populated dynamically
self.genres = []
self.showid = ''
self.name = ''
self.link = ''
self.country = ''
self.status = ''
self.classification = ''
self.started = 0
self.ended = 0
self.seasons = 0
show = feeds.search(self.shortname, node='show')
if not show:
raise ShowNotFound(name)
# dynamically mapping the xml tags to properties:
for elem in show:
if not elem.tag == 'seasons': # we'll set this later
# these properties should be ints
if elem.tag in ('started', 'ended'):
self.__dict__[elem.tag] = int(elem.text)
# these are fine as strings
else:
self.__dict__[elem.tag] = elem.text
self.genres = [g.text for g in show.find('genres')]
# and now grabbing the episodes
eplist = feeds.episode_list(self.showid, node='Episodelist')
# populating the episode list
for season in eplist:
try:
snum = int(season.attrib['no'])
except KeyError:
pass # TODO: adding handeling for specials and movies
# bsp: http://www.tvrage.com/feeds/episode_list.php?sid=3519
else:
self.episodes[snum] = Season()
for episode in season:
epnum = int(episode.find('seasonnum').text)
self.episodes[snum][epnum] = Episode(
self.name,
snum,
episode.find('airdate').text,
episode.find('title').text,
episode.find('link').text,
epnum,
episode.find('prodnum').text,
)
if snum > 0:
self.seasons = max(snum, self.seasons)
self.episodes[self.seasons].is_current = True
@property
def pilot(self):
"""returns the pilot/1st episode"""
return self.episodes[1][1]
@property
def current_season(self):
"""returns the season currently running on tv"""
if not self.ended: # still running
return self.episodes[self.seasons]
else:
raise ShowHasEnded(self.name)
@property
def next_episode(self):
"""returns the next upcoming episode"""
try:
return self.upcoming_episodes.next()
except StopIteration:
raise NoNewEpisodesAnnounced(self.name)
@property
def upcoming_episodes(self):
"""returns all upcoming episodes that have been annouced yet"""
today = date.today()
for e in self.current_season.values():
if (e.airdate is not None) and (e.airdate >= today):
yield e
@property
def latest_episode(self):
"""returns the latest episode that has aired already"""
today = date.today()
eps = self.season(self.seasons).values()
eps.reverse()
for e in eps:
if (e.airdate is not None) and (e.airdate < today):
return e
@property
def synopsis(self):
"""scraps the synopsis from the show's tvrage page using a regular
expression. This method might break when the page changes. unfortunatly
the episode summary isnt available via one of the xml feeds"""
try:
page = _fetch(self.link).read()
synopsis = parse_synopsis(page)
return synopsis
except Exception, e:
print('Show.synopsis:urlopen: %s, %s' % (self, e))
return 'No Synopsis available'
def season(self, n):
"""returns the nth season as dict of episodes"""
return self.episodes[n]
ckreutzer-python-tvrage-c8e9781/tvrage/exceptions.py 0000664 0000000 0000000 00000003621 12025677021 0022736 0 ustar 00root root 0000000 0000000 #!/usr/bin/env python
# Copyright (c) 2010, Christian Kreutzer
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions, and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions, and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the author of this software nor the name of
# contributors to this software may be used to endorse or promote products
# derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
class BaseError(Exception):
def __init__(self, value):
self.value = value
def __str__(self):
return repr(self.value)
class ShowHasEnded(BaseError):
pass
class NoNewEpisodesAnnounced(BaseError):
pass
class FinaleMayNotBeAnnouncedYet(BaseError):
pass
class ShowNotFound(BaseError):
pass
ckreutzer-python-tvrage-c8e9781/tvrage/feeds.py 0000664 0000000 0000000 00000005256 12025677021 0021651 0 ustar 00root root 0000000 0000000 # Copyright (c) 2009, Christian Kreutzer
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions, and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions, and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the author of this software nor the name of
# contributors to this software may be used to endorse or promote products
# derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
from util import _fetch
from urllib2 import quote
try:
import xml.etree.cElementTree as et
except ImportError:
import xml.etree.ElementTree as et
BASE_URL = 'http://www.tvrage.com/feeds/%s.php?%s=%s'
def _fetch_xml(url, node=None):
"""fetches the response of a simple xml-based webservice. If node is
omitted the root of the parsed xml doc is returned as an ElementTree object
otherwise the requested node is returned"""
xmldoc = _fetch(url)
result = et.parse(xmldoc)
root = result.getroot()
if not node:
retval = root
else:
retval = root.find(node)
return retval
def search(show, node=None):
return _fetch_xml(BASE_URL % ('search', 'show', quote(show)), node)
def full_search(show, node=None):
return _fetch_xml(BASE_URL % ('full_search', 'show', quote(show)), node)
def showinfo(sid, node=None):
return _fetch_xml(BASE_URL % ('showinfo', 'sid', sid), node)
def episode_list(sid, node=None):
return _fetch_xml(BASE_URL % ('episode_list', 'sid', sid), node)
def full_show_info(sid, node=None):
return _fetch_xml(BASE_URL % ('full_show_info', 'sid', sid), node)
ckreutzer-python-tvrage-c8e9781/tvrage/quickinfo.py 0000664 0000000 0000000 00000004572 12025677021 0022553 0 ustar 00root root 0000000 0000000 # Copyright (c) 2009, Christian Kreutzer
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions, and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions, and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the author of this software nor the name of
# contributors to this software may be used to endorse or promote products
# derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
from urllib2 import urlopen, URLError, quote
from util import _fetch
from exceptions import ShowNotFound
BASE_URL = 'http://services.tvrage.com/tools/quickinfo.php'
def fetch(show, exact=False, ep=None):
query_string = '?show=' + quote(show)
if exact:
query_string = query_string + '&exact=1'
if ep:
query_string = query_string + '&ep=' + quote(ep)
resp = _fetch(BASE_URL + query_string).read()
show_info = {}
if 'No Show Results Were Found For' in resp:
raise ShowNotFound(show)
else:
data = resp.replace('', '').splitlines()
for line in data:
k, v = line.split('@')
# TODO: use datetimeobj for dates
show_info[k] = (v.split(' | ') if ' | ' in v else
(v.split('^') if '^' in v else v))
return show_info
ckreutzer-python-tvrage-c8e9781/tvrage/util.py 0000664 0000000 0000000 00000005533 12025677021 0021536 0 ustar 00root root 0000000 0000000 # Copyright (c) 2009, Christian Kreutzer
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions, and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions, and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the author of this software nor the name of
# contributors to this software may be used to endorse or promote products
# derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
from urllib2 import urlopen, URLError
from BeautifulSoup import BeautifulSoup
class TvrageError(Exception):
""" Base class for custom exceptions"""
def __init__(self, msg):
self.msg = msg
def __str__(self):
return self.msg
class TvrageRequestError(TvrageError):
""" Wrapper for HTTP 400 """
pass
class TvrageNotFoundError(TvrageError):
""" Wrapper for HTTP 404"""
pass
class TvrageInternalServerError(TvrageError):
""" Wrapper for HTTP 500"""
pass
def _fetch(url):
try:
result = urlopen(url)
except URLError, e:
if 400 == e.code:
raise TvrageRequestError(str(e))
elif 404 == e.code:
raise TvrageNotFoundError(str(e))
elif 500 == e.code:
raise TvrageInternalServerError(str(e))
else:
raise TvrageError(str(e))
except Exception, e:
raise TvrageError(str(e))
else:
return result
def parse_synopsis(page, cleanup=None):
soup = BeautifulSoup(page)
try:
result = soup.find('div', attrs={'class': 'show_synopsis'}).text
#cleaning up a litle bit
if cleanup:
result, _ = result.split(cleanup)
return result
except AttributeError, e:
print('parse_synopyis - BeautifulSoup.find(): %s' % e)