pax_global_header00006660000000000000000000000064120256770210014514gustar00rootroot0000000000000052 comment=c8e97815033eb3769c2e38ca5389a0b4e7bcb970 ckreutzer-python-tvrage-c8e9781/000077500000000000000000000000001202567702100167115ustar00rootroot00000000000000ckreutzer-python-tvrage-c8e9781/.hgignore000066400000000000000000000001341202567702100205120ustar00rootroot00000000000000# use glob syntax. syntax: glob .git* *.pyc *~ build .DS_Store *.kpf *.swp *.komodoproject ckreutzer-python-tvrage-c8e9781/.hgtags000066400000000000000000000010031202567702100201610ustar00rootroot00000000000000e4a0ad3ca58cb998803788f05d2aeacb07660153 0.1 850b1bf416ca9250ae037ddb73dda88de920a17e 0.1.1 5bbd09e0b2a2268b80406724e9cfddc2a62b84f8 0.1.2 08a137262739e63820ac05bcf7f85d17e8ae6c52 0.1.3 49f70aed7f8509a74070791ba5378ed2ec3738a3 0.1.4 935443d40a59984e564ac7991a08dc3e79b20644 0.2.0 1e9600f418ca1eea2e1d8c911e25a62997fb9029 0.2.1 e371d46a4464b4c80c23b665a9fad14ddd62fcc7 0.3.0 f9a2e8f8d065a0646f6a0befaf6cda4d6dee59b0 0.3.1 083ae6d7f6640d32b0cfd41da40426bd80fee16e 0.3.2 110bc7073ccb97413bb5daee9db00a992c8070ff 0.4.0 ckreutzer-python-tvrage-c8e9781/AUTHORS000066400000000000000000000003241202567702100177600ustar00rootroot00000000000000Original Author: ------------ * Christian Kreutzer Contributors ------------ * topdeck (http://bitbucket.org/topdeck) * samueltardieu (http://bitbucket.org/samueltardieu) * chevox (https://bitbucket.org/chexov) ckreutzer-python-tvrage-c8e9781/LICENSE000066400000000000000000000027671202567702100177320ustar00rootroot00000000000000Copyright (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.ckreutzer-python-tvrage-c8e9781/NEWS000066400000000000000000000007121202567702100174100ustar00rootroot000000000000000.4.1 ===== * some fixes for `Episodes.recap` * moved tests to separate folder * code is now pep8 compliant 0.4.0 ===== * added the following fields to the `api.Episode` class: `id`, `recap_url` and `recap` * switched from parsing the `Episode.summary` and `Show.summary` with a regexp to BeautifulSoup which hopefully will be more stable than the regexp approach * BeautifulSoup is now an external dependency (see requiremets.txt) * added this file ckreutzer-python-tvrage-c8e9781/README.rst000066400000000000000000000216501202567702100204040ustar00rootroot00000000000000About ----- python-tvrage is a python based object oriented client interface for tvrage.com's XML based api `feeds`_. .. _feeds: http://www.tvrage.com/xmlfeeds.php Installation ------------ Install from sources:: $ python setup.py install The easiest way to get started with python-tvrage is to use `easy_install` or `pip`:: $ easy_install -U python-tvrage For `pip` you have to replace **'easy_install'** with **'pip install'**. Also your platform may require the use of `sudo` Documentation ------------- The `tvrage` package consists of three modules - the tvrage.feeds module provides a wrapper function for each of tvrage's XML-feeds - the tvrage.api module provides an clean and object oriented interface on top of those services - the tvrage.quickinfo module is a simple pythonic wrapper for tvrage's quickinfo api. Values are returned as python dictionaries rather than dedicated objects for tv shows and episodes Fetching XML data +++++++++++++++++ The `tvrage.feeds` module provides very basic and low level access to the tvrage.com data feeds. For more complex use cases it is recomended to use the object oriented module `tvrage.api`. Note: All functions in the `tvrage.feeds` module return XML data as ElementTree objects. searching for TV shows using the general `search` feed (returns fuzzy search results):: $ python >>> from tvrage import feeds >>> from xml.etree.ElementTree import tostring >>> >>> doctor_who = feeds.search('doctorwho2005') >>> for node in doctor_who: ... print(tostring(node)) ... 3332 Doctor Who (2005) http://www.tvrage.com/DoctorWho_2005 UK 2005 0 5 Returning Series Scripted ActionAdventureSci-Fi 3331 Doctor Who http://www.tvrage.com/Doctor_Who_1963 UK 1963 1989 27 Canceled/Ended Scripted ActionAdventureSci-Fi using `full_search` includes all availabe meta data about the shows into the search results:: >>> doctor_who = feeds.full_search('doctorwho2005') >>> print(tostring(doctor_who[0])) 3332 Doctor Who (2005) http://www.tvrage.com/DoctorWho_2005 UK Mar/26/2005 5 Returning Series 50 Scripted ActionAdventureSci-Fi BBC One 19:00 Saturday Docteur Who Dr. Who Tímaflakk Torchwood Доктор Кой Доктор Кто דוקטור הו डॉक्टर हू 異世奇人 The `showinfo` feed retrieves all meta data about one single show using the given `showid`. The result is identical to one element from the `full_search` results. The `episode_list` feed returns all meta data about episodes of a TV show sorted by season. The optional `node` argument causes the function to return the desired XML node as ElementTree object:: >>> doctor_who_eps = feeds.episode_list('3332', node='Episodelist') >>> print(tostring(doctor_who_eps[0])) 1 01 101 2005-03-26 http://www.tvrage.com/DoctorWho_2005/episodes/52117 Rose 2 02 102 2005-04-02 http://www.tvrage.com/DoctorWho_2005/episodes/52118 The End of the World ... The `full_show_info` feed combines the results of both `showinfo` and `episode_list`. Using objects +++++++++++++ The module `tvrage.api` provides wrapper classes for tvrage.com's data feeds. It contains the following classes: `Show`, `Season` and `Episode`. Working with TV show objects:: $ python >>> import tvrage.api >>> doctor_who = tvrage.api.Show('doctorwho2005') >>> doctor_who.country 'UK' >>> doctor_who.current_season {1: Doctor Who (2005) 5x01 The Eleventh Hour, 2: Doctor Who (2005) 5x02 The Beast... } >>> doctor_who.ended 0 >>> doctor_who.episodes {1: {1: Doctor Who (2005) 1x01 Rose, 2: Doctor Who (2005) 1x02 The End of the World, ... }} >>> doctor_who.genres ['Action', 'Adventure', 'Sci-Fi'] >>> doctor_who.latest_episode Doctor Who (2005) 5x04 The Time of Angels (1) >>> doctor_who.next_episode Doctor Who (2005) 5x05 Flesh and Stone (2) >>> doctor_who.link 'http://www.tvrage.com/DoctorWho_2005' >>> doctor_who.name 'Doctor Who (2005)' >>> doctor_who.pilot Doctor Who (2005) 1x01 Rose >>> doctor_who.season(2) {1: Doctor Who (2005) 2x01 New Earth, 2: Doctor Who (2005) 2x02 Tooth and Claw, ... } >>> doctor_who.seasons 5 >>> doctor_who.shortname 'doctorwho2005' >>> doctor_who.showid '3332' >>> doctor_who.started 2005 >>> doctor_who.status 'Returning Series' >>> doctor_who.upcoming_episodes 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.txt000066400000000000000000000000251202567702100221720ustar00rootroot00000000000000BeautifulSoup==3.2.1 ckreutzer-python-tvrage-c8e9781/setup.py000066400000000000000000000017161202567702100204300ustar00rootroot00000000000000#!/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/000077500000000000000000000000001202567702100200535ustar00rootroot00000000000000ckreutzer-python-tvrage-c8e9781/tests/api_tests.py000066400000000000000000000154421202567702100224260ustar00rootroot00000000000000#!/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.py000066400000000000000000000063041202567702100236420ustar00rootroot00000000000000#!/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/000077500000000000000000000000001202567702100202015ustar00rootroot00000000000000ckreutzer-python-tvrage-c8e9781/tvrage/__init__.py000066400000000000000000000001151202567702100223070ustar00rootroot00000000000000 __version__ = '0.4.1' __author__ = 'Christian Kreutzer' __license__ = 'BSD' ckreutzer-python-tvrage-c8e9781/tvrage/api.py000066400000000000000000000204641202567702100213320ustar00rootroot00000000000000# 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.py000066400000000000000000000036211202567702100227360ustar00rootroot00000000000000#!/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.py000066400000000000000000000052561202567702100216510ustar00rootroot00000000000000# 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.py000066400000000000000000000045721202567702100225530ustar00rootroot00000000000000# 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.py000066400000000000000000000055331202567702100215360ustar00rootroot00000000000000# 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)