Ejemplo n.º 1
0
    def __init__(self, api_key, **kwargs):
        self.config = dict()

        #cache old searches to avoid hitting the server
        self.search_buffer = dict()

        #Store the path to where we are
        self.path = os.path.abspath(os.path.dirname(__file__))

        if 'force_lang' in kwargs:
            logger.warning(
                "'force_lang' keyword argument is deprecated as of version 0.4"
            )

        #extract all argument and store for later use
        self.config['api_key'] = api_key
        self.config['cache_dir'] = kwargs.get(
            "cache_dir", os.path.join(tempfile.gettempdir(), name))
        self.config['actors'] = kwargs.get('actors', False)
        self.config['banners'] = kwargs.get('banners', False)
        self.config['ignore_case'] = kwargs.get('ignore_case', False)

        #Create the loader object to use
        self.loader = Loader(self.config['cache_dir'])

        #Create the list of available mirrors
        tree = generate_tree(
            self.loader.load(__mirrors__.format(**self.config)))
        self.mirrors = MirrorList(tree)
Ejemplo n.º 2
0
    def __init__(self, api_key, **kwargs):
        self.config = dict()

        #cache old searches to avoid hitting the server
        self.search_buffer = dict()

        #Store the path to where we are
        self.path = os.path.abspath(os.path.dirname(__file__))

        if 'force_lang' in kwargs:
            logger.warning("'force_lang' keyword argument is deprecated as of version 0.4")

        #extract all argument and store for later use
        self.config['api_key'] = api_key
        self.config['cache_dir'] = kwargs.get("cache_dir", os.path.join(tempfile.gettempdir(), name))
        self.config['actors'] = kwargs.get('actors', False)
        self.config['banners'] = kwargs.get('banners', False)
        self.config['ignore_case'] = kwargs.get('ignore_case', False)

        #Create the loader object to use
        self.loader = Loader(self.config['cache_dir'])

        #Create the list of available mirrors
        tree = generate_tree(self.loader.load(__mirrors__.format(**self.config)))
        self.mirrors = MirrorList(tree)
Ejemplo n.º 3
0
    def __init__(self, api_key, **kwargs):
        self.config = dict()

        #cache old searches to avoid hitting the server
        self.search_buffer = dict()

        #Store the path to where we are
        self.path = os.path.abspath(os.path.dirname(__file__))

        #extract all argument and store for later use
        self.config['force_lang'] = kwargs.get("force_lang", False)
        self.config['api_key'] = api_key
        self.config['cache_dir'] = kwargs.get("cache_dir", os.path.join(tempfile.gettempdir(), name))
        self.config['actors'] = kwargs.get('actors', False)
        self.config['banners'] = kwargs.get('banners', False)
        self.config['ignore_case'] = kwargs.get('ignore_case', False)

        #Create the loader object to use
        self.loader = Loader(self.config['cache_dir'])

        language_file = resource_filename(__name__, 'data/languages.xml')

        #If requested, update the local language file from the server
        if self.config['force_lang']:
            logger.debug("updating Language file from server")
            with open(language_file, "wt", encoding='utf-8') as languages:
                language_data = self.loader.load(__languages__.format(**self.config))
                languages.write(language_data)

        #Setup the list of supported languages
        self.languages = LanguageList(generate_tree(language_file))

        #Create the list of available mirrors
        tree = generate_tree(self.loader.load(__mirrors__.format(**self.config)))
        self.mirrors = MirrorList(tree)
Ejemplo n.º 4
0
    def __init__(self, api_key, **kwargs):
        self.config = dict()

        # cache old searches to avoid hitting the server
        self.search_buffer = dict()

        # extract all argument and store for later use
        self.config['api_key'] = api_key
        self.config['cache_dir'] = kwargs.get(
            "cache_dir",
            make_unicode(os.path.join(tempfile.gettempdir(), __NAME__)))

        self.config['actors'] = kwargs.get('actors', False)
        self.config['banners'] = kwargs.get('banners', False)
        self.config['ignore_case'] = kwargs.get('ignore_case', False)

        # Create the loader object to use
        self.loader = Loader(self.config['cache_dir'],
                             timeout=kwargs.get('timeout', None))

        # Create the list of available mirrors
        tree = generate_tree(self.loader.load(mirrors.format(**self.config)))
        self.mirrors = MirrorList(tree)
Ejemplo n.º 5
0
    def __init__(self, api_key, **kwargs):
        self.config = dict()

        # cache old searches to avoid hitting the server
        self.search_buffer = dict()

        # extract all argument and store for later use
        self.config['api_key'] = api_key
        self.config['cache_dir'] = kwargs.get("cache_dir",
                                              make_unicode(os.path.join(tempfile.gettempdir(), __NAME__)))

        self.config['actors'] = kwargs.get('actors', False)
        self.config['banners'] = kwargs.get('banners', False)
        self.config['ignore_case'] = kwargs.get('ignore_case', False)

        # Create the loader object to use
        self.loader = Loader(self.config['cache_dir'], timeout=kwargs.get('timeout', None))

        # Create the list of available mirrors
        tree = generate_tree(self.loader.load(mirrors.format(**self.config)))
        self.mirrors = MirrorList(tree)
Ejemplo n.º 6
0
class TVDB(object):
    """
    :param api_key: The API key to use to communicate with the server
    :param kwargs:

    This is the main entry point for the API. The functionality of the API is
    controlled by configuring the keyword arguments. The supported keyword
    arguments are:

    * **cache_dir** (default=/<system tmp dir>/pytvdbapi/). Specifies the
      directory to use for caching the server requests.

    .. versionadded:: 0.3

    * **actors** (default=False) The extended actor information is stored in a
      separate XML file and would require an additional request to the server
      to obtain. To limit the resource usage, the actor information will only
      be loaded when explicitly requested.

      .. note:: The :class:`Show()` object always contain a list of actor
        names.

    * **banners** (default=False) The extended banner information is stored in a
      separate XML file and would require an additional request to the server
      to obtain. To limit the resource usage, the banner information will only
      be loaded when explicitly requested.

    .. versionadded:: 0.4

    * **ignore_case** (default=False) If set to True, all attributes on the
      :class:`Show` and :class:`Episode` instances will be accessible in a
      case insensitive manner. If set to False, the default, all
      attributes will be case sensitive and retain the same casing
      as provided by `thetvdb.com <http://thetvdb.com>`_.

    * **timeout** (default=None) When set to a number, will cause the http
    request to timeout after that number of seconds.

    """

    @unicode_arguments
    def __init__(self, api_key, **kwargs):
        self.config = dict()

        # cache old searches to avoid hitting the server
        self.search_buffer = dict()

        # extract all argument and store for later use
        self.config['api_key'] = api_key
        self.config['cache_dir'] = kwargs.get("cache_dir",
                                              make_unicode(os.path.join(tempfile.gettempdir(), __NAME__)))

        self.config['actors'] = kwargs.get('actors', False)
        self.config['banners'] = kwargs.get('banners', False)
        self.config['ignore_case'] = kwargs.get('ignore_case', False)

        # Create the loader object to use
        self.loader = Loader(self.config['cache_dir'], timeout=kwargs.get('timeout', None))

        # Create the list of available mirrors
        tree = generate_tree(self.loader.load(mirrors.format(**self.config)))
        self.mirrors = MirrorList(tree)

    @unicode_arguments
    def search(self, show, language, cache=True):
        """
        :param show: The show name to search for
        :param language: The language abbreviation to search for. E.g. "en"
        :param cache: If False, the local cache will not be used and the
            resources will be reloaded from server.
        :return: A :class:`Search()` instance
        :raise: :exc:`pytvdbapi.error.TVDBValueError`

        Searches the server for a show with the provided show name in the
        provided language. The language should be one of the supported
        language abbreviations or it could be set to *all* to search all
        languages. It will raise :class:`pytvdbapi.error.TVDBValueError` if
        an invalid language is provided.

        Searches are always cached within a session to make subsequent
        searches with the same parameters fast. If *cache*
        is set to True searches will also be cached across sessions,
        this is recommended to increase speed and to reduce the workload of
        the servers.

        Example::

            >>> from pytvdbapi import api
            >>> db = api.TVDB("B43FF87DE395DF56")
            >>> result = db.search("House", "en")

            >>> print(result[0])
            <Show - House>

            >>> for show in result:
            ...     print(show) # doctest: +ELLIPSIS
            <Show - House>
            ...
        """

        logger.debug(u"Searching for {0} using language {1}".format(show, language))

        if language != u'all' and language not in __LANGUAGES__:
            raise error.TVDBValueError(u"{0} is not a valid language".format(language))

        if (show, language) not in self.search_buffer or not cache:
            context = {'series': quote(make_bytes(show)), "language": language}
            data = generate_tree(self.loader.load(search.format(**context), cache))
            shows = [Show(d, self, language, self.config) for d in parse_xml(data, "Series")]

            self.search_buffer[(show, language)] = shows

        return Search(self.search_buffer[(show, language)], show, language)

    @unicode_arguments
    def get_series(self, series_id, language, id_type='tvdb', cache=True):
        """
        .. versionadded:: 0.4
        .. versionchanged:: 0.5 Added *id_type* parameter

        :param series_id: The Show Id to fetch
        :param language: The language abbreviation to search for. E.g. "en"
        :param id_type: Information about what kind of id is provided. Should be one of *('tvdb', 'imdb',
            'zap2it')*
        :param cache: If False, the local cache will not be used and the
                    resources will be reloaded from server.

        :return: A :class:`Show()` instance
        :raise: :exc:`pytvdbapi.error.TVDBValueError`, :exc:`pytvdbapi.error.TVDBIdError`

        Provided a valid Show ID, the data for the show is fetched and a
        corresponding :class:`Show()` object is returned.

        Example::

            >>> from pytvdbapi import api
            >>> db = api.TVDB("B43FF87DE395DF56")
            >>> show = db.get_series( 79349, "en" )  # Load Dexter
            >>> print(show.SeriesName)
            Dexter
        """
        if id_type not in ('tvdb', 'imdb', 'zap2it'):
            raise error.TVDBValueError("Invalid id type")
        elif language != 'all' and language not in __LANGUAGES__:
            raise error.TVDBValueError(u"{0} is not a valid language".format(language))

        # Map id type to url template
        __url__ = {'tvdb': series, 'imdb': imdbid, 'zap2it': zap2itid}

        try:
            series_id = text_type(series_id)
        except ValueError:
            raise error.TVDBValueError(
                "Invalid id type, expected {0} or {1}, got {2}".format(text_type, int_types, type(series_id)))

        if id_type == 'imdb':
            series_id = series_id[2:] if series_id.startswith('tt') else series_id
        elif id_type == 'zap2it':
            series_id = series_id if series_id.startswith('EP') else u'EP' + series_id.rjust(8, '0')

        logger.debug(
            u"Getting series with id {0}({2}) with language {1}".format(series_id, language, id_type))

        context = {'seriesid': series_id, "language": language,
                   'mirror': self.mirrors.get_mirror(TypeMask.XML).url,
                   'api_key': self.config['api_key'], 'imdbid': series_id, 'zap2itid': series_id}

        url = __url__[id_type].format(**context)
        logger.debug(u'Getting series from {0}'.format(url))

        try:
            data = self.loader.load(url, cache)
        except error.TVDBNotFoundError:
            raise error.TVDBIdError(u"Series id {0} not found".format(series_id))

        data = generate_tree(data)

        series_data = parse_xml(data, "Series")

        if len(series) == 0:
            raise error.BadData("Bad data received")
        else:
            return Show(series_data[0], self, language, self.config, data)

    @unicode_arguments
    @deprecate_episode_id
    def get_episode(self, language, method="id", cache=True, **kwargs):
        """
        .. versionadded:: 0.4
        .. versionchanged:: 0.5 Added the possibility to get an episode using *default*, *dvd*, and *absolute*
            sort order

        :param episode_id: *Deprecated in 0.5* Use the *episodeid* keyword argument with the *id*
            method instead
        :param language: The language abbreviation to search for. E.g. "en"
        :param cache: If False, the local cache will not be used and the
                    resources will be reloaded from server.
        :param method: (default=id) Specify what method should be used to get the episode. Depending on
            what method is specified, different parameters must be passed as keyword arguments. Should be one
            of (id, default, dvd, absolute).
        :param kwargs: *episodeid*, *seriesid*, *seasonnumber*, *episodenumber* and *absolutenumber*. See
            the examples for information on how to use them.
        :return: An :class:`Episode()` instance
        :raise: :exc:`pytvdbapi.error.TVDBValueError`, :exc:`pytvdbapi.error.BadData`

        Retrieves a single episode. Depending on what method is specified different criteria can be used to
        retrieve the episode.

        Examples:

        Load an episode using the episode id

        >>> from pytvdbapi import api
        >>> db = api.TVDB("B43FF87DE395DF56")
        >>> ep = db.get_episode("en", episodeid=308834)  # id is the default method
        >>> print(ep.EpisodeName)
        Crocodile

        Load an episode using dvd and default sort order

        >>> ep = db.get_episode("en", "dvd", seasonnumber=2, episodenumber=5, seriesid=79349)
        >>> print(ep.EpisodeName)
        The Dark Defender

        >>> ep = db.get_episode("en", "default", seasonnumber=2, episodenumber=6, seriesid=79349)
        >>> print(ep.EpisodeName)
        Dex, Lies, and Videotape

        Load an episode using the absolute number

        >>> ep = db.get_episode("en", "absolute", absolutenumber=19, seriesid=79349)
        >>> print(ep.EpisodeName)
        That Night, A Forest Grew

        Under some circumstances the backend server fails to return a proper **404** file not found error
        response when the requested episode can not be found, but instead returns a valid HTML file with
        the content *404 file not found*. For this reason it is required to check for both
        :exc:`pytvdbapi.error.TVDBValueError` and :exc:`pytvdbapi.error.BadData` to detect an issue
        downloading the episode.

        >>> from pytvdbapi.error import BadData
        >>> from pytvdbapi.error import TVDBNotFoundError
        >>> try:
        ...    ep = db.get_episode("en", episodeid=308834)
        ... except TVDBNotFoundError:
        ...    # this is the standard 404 error code returned from the server
        ...    pass
        ... except BadData:
        ...     # This is when the server returns a 200 code but with a HTML page saying 404 Nothing found
        ...     pass

        .. Note:: When the :class:`Episode()` is loaded using :func:`get_episode()`
            the *season* attribute used to link the episode with a season will be None.
        """
        methods = {"default": default_order, "dvd": dvd_order, "absolute": absolute_order, "id": episode}

        if language != 'all' and language not in __LANGUAGES__:
            raise error.TVDBValueError(u"{0} is not a valid language".format(language))

        context = {"language": language,
                   'mirror': self.mirrors.get_mirror(TypeMask.XML).url,
                   'api_key': self.config['api_key']}

        kwargs.update(context)

        try:
            url = methods[method]
        except KeyError:
            raise error.TVDBValueError(u"{0} is not a valid get method".format(method))

        try:
            url = url.format(**kwargs)
        except KeyError:
            raise error.TVDBValueError("Missing arguments for method {0}".format(method))

        logger.debug(u'Getting episode from {0}'.format(url))

        data = self.loader.load(url, cache)
        data = generate_tree(data)

        episodes = parse_xml(data, "Episode")

        if len(episodes) == 0:
            raise error.BadData("Bad data received")
        else:
            return Episode(episodes[0], None, self.config)

    @unicode_arguments
    def get_episode_by_air_date(self, series_id, language, air_date, cache=True):
        """
        .. versionadded:: 0.5

        :param series_id: The TVDB series id of the episode
        :param language: The language to search for. Should be a two letter abbreviation e.g. *en*.
        :param air_date: The air date to search for. Should be of type :class:`datetime.date`
        :type air_date: datetime.date
        :param cache: If False, the local cache will not be used and the
                    resources will be reloaded from server.

        :return: If found, an :class:`Episode` instance
        :raise: :exc:`pytvdbapi.error.TVDBValueError`


        .. Note:: When the :class:`Episode()` is loaded using :func:`get_episode_by_air_date`
            the *season* attribute used to link the episode with a season will be None.
        """
        if type(air_date) not in (datetime.date,):
            raise error.TVDBValueError("air_date should be of type datetime.date")
        elif language != 'all' and language not in __LANGUAGES__:
            raise error.TVDBValueError(u"{0} is not a valid language".format(language))

        context = {'seriesid': series_id, 'airdate': air_date, "language": language,
                   'mirror': self.mirrors.get_mirror(TypeMask.XML).url, 'api_key': self.config['api_key']}

        url = airdate.format(**context)
        logger.debug(u'Getting episode from {0}'.format(url))

        data = self.loader.load(url, cache)

        data = generate_tree(data)

        # The xml has an "Error" element in it if no episode was found
        if has_element(data, 'Error'):
            raise error.TVDBNotFoundError(u"".format())

        episodes = parse_xml(data, "Episode")

        if len(episodes) == 0:
            raise error.BadData("Bad data received")
        else:
            return Episode(episodes[0], None, self.config)
Ejemplo n.º 7
0
class TVDB(object):
    """
    :param api_key: The API key to use to communicate with the server
    :param kwargs:

    This is the main entry point for the API. The functionality of the API is
    controlled by configuring the keyword arguments. The supported keyword
    arguments are:

    * *force_lang* (default=False). Deprecated in version 0.4. Using it will
      have no affect but will issue a warning in the log file.

    * *cache_dir* (default=/<system tmp dir>/pytvdbapi/). Specifies the
      directory to use for caching the server requests.

    .. versionadded:: 0.3

    * *actors* (default=False) The extended actor information is stored in a
      separate XML file and would require an additional request to the server
      to obtain. To limit the resource usage, the actor information will only
      be loaded when explicitly requested.

      .. note:: The :class:`Show()` object always contain a list of actor
        names.

    * *banners* (default=False) The extended banner information is stored in a
      separate XML file and would require an additional request to the server
      to obtain. To limit the resource usage, the banner information will only
      be loaded when explicitly requested.

    .. versionadded:: 0.4

    * *ignore_case* (default=False) If set to True, all attributes on the
      :class:`Show` and :class:`Episode` instances will be accessible in a
      case insensitive manner. If set to False, the default, all
      attributes will be case sensitive and retain the same casing
      as provided by `thetvdb.com <http://thetvdb.com>`_.
    """

    def __init__(self, api_key, **kwargs):
        self.config = dict()

        #cache old searches to avoid hitting the server
        self.search_buffer = dict()

        #Store the path to where we are
        self.path = os.path.abspath(os.path.dirname(__file__))

        if 'force_lang' in kwargs:
            logger.warning("'force_lang' keyword argument is deprecated as of version 0.4")

        #extract all argument and store for later use
        self.config['api_key'] = api_key
        self.config['cache_dir'] = kwargs.get("cache_dir", os.path.join(tempfile.gettempdir(), name))
        self.config['actors'] = kwargs.get('actors', False)
        self.config['banners'] = kwargs.get('banners', False)
        self.config['ignore_case'] = kwargs.get('ignore_case', False)

        #Create the loader object to use
        self.loader = Loader(self.config['cache_dir'])

        #Create the list of available mirrors
        tree = generate_tree(self.loader.load(__mirrors__.format(**self.config)))
        self.mirrors = MirrorList(tree)

    def search(self, show, language, cache=True):
        """
        :param show: The show name to search for
        :param language: The language abbreviation to search for. E.g. "en"
        :param cache: If False, the local cache will not be used and the
            resources will be reloaded from server.
        :return: A :class:`Search()` instance
        :raise: :class:`pytvdbapi.error.TVDBValueError`

        Searches the server for a show with the provided show name in the
        provided language. The language should be one of the supported
        language abbreviations or it could be set to *all* to search all
        languages. It will raise :class:`pytvdbapi.error.TVDBValueError` if
        an invalid language is provided.

        Searches are always cached within a session to make subsequent
        searches with the same parameters really cheap and fast. If *cache*
        is set to True searches will also be cached across sessions,
        this is recommended to increase speed and to reduce the workload of
        the servers.

        Example::

            >>> from pytvdbapi import api
            >>> db = api.TVDB("B43FF87DE395DF56")
            >>> search = db.search("Dexter", "en")
            >>> for s in search:
            ...     print(s)
            ...
            <Show - Dexter>
        """
        logger.debug("Searching for {0} using language {1}".format(show, language))

        if language != 'all' and language not in __LANGUAGES__:
            raise error.TVDBValueError("{0} is not a valid language".format(language))

        if (show, language) not in self.search_buffer or not cache:
            if sys.version_info < (3, 0):
                show = str(show.encode('utf-8'))

            context = {'series': quote(show), "language": language}
            data = generate_tree(self.loader.load(__search__.format(**context), cache))
            shows = [Show(d, self, language, self.config) for d in parse_xml(data, "Series")]

            self.search_buffer[(show, language)] = shows

        return Search(self.search_buffer[(show, language)], show, language)

    def get(self, series_id, language, cache=True):
        """
        .. versionadded:: 0.3
        .. deprecated:: 0.4 Use :func:`get_series` instead.

        :param series_id: The Show Id to fetch
        :param language: The language abbreviation to search for. E.g. "en"
        :param cache: If False, the local cache will not be used and the
                    resources will be reloaded from server.

        :return: A :class:`Show()` instance
        :raise: :class:`pytvdbapi.error.TVDBValueError`, :class:`pytvdbapi.error.TVDBIdError`

        """

        logger.warning("Using deprecated function 'get'. Use 'get_series' instead")
        return self.get_series(series_id, language, cache)

    def get_series(self, series_id, language, cache=True):
        """
        .. versionadded:: 0.4

        :param series_id: The Show Id to fetch
        :param language: The language abbreviation to search for. E.g. "en"
        :param cache: If False, the local cache will not be used and the
                    resources will be reloaded from server.

        :return: A :class:`Show()` instance
        :raise: :class:`pytvdbapi.error.TVDBValueError`, :class:`pytvdbapi.error.TVDBIdError`

        Provided a valid Show ID, the data for the show is fetched and a
        corresponding :class:`Show()` object is returned.

        Example::

            >>> from pytvdbapi import api
            >>> db = api.TVDB("B43FF87DE395DF56")
            >>> show = db.get( 79349, "en" )
            >>> show.id
            79349

            >>> show.SeriesName
            'Dexter'

        """

        logger.debug("Getting series with id {0} with language {1}".format(series_id, language))

        if language != 'all' and language not in __LANGUAGES__:
            raise error.TVDBValueError("{0} is not a valid language".format(language))

        context = {'seriesid': series_id, "language": language,
                   'mirror': self.mirrors.get_mirror(TypeMask.XML).url,
                   'api_key': self.config['api_key']}

        url = __series__.format(**context)
        try:
            data = self.loader.load(url, cache)
        except error.TVDBNotFoundError:
            raise error.TVDBIdError("Series id {0} not found".format(series_id))
        except error.ConnectionError as _error:
            logger.debug("Unable to connect to URL: {0}. {1}".format(url, _error))
            raise

        if data.strip():
            data = generate_tree(data)
        else:
            raise error.TVDBIdError("No Show with id {0} found".format(series_id))

        series = parse_xml(data, "Series")
        assert len(series) <= 1, "Should not find more than one series"

        if len(series) >= 1:
            return Show(series[0], self, language, self.config)
        else:
            raise error.TVDBIdError("No Show with id {0} found".format(series_id))

    def get_episode(self, episode_id, language, cache=True):
        """
        .. versionadded:: 0.4

        :param episode_id: The Episode Id to fetch
        :param language: The language abbreviation to search for. E.g. "en"
        :param cache: If False, the local cache will not be used and the
                    resources will be reloaded from server.

        :return: An :class:`Episode()` instance
        :raise: :class:`pytvdbapi.error.TVDBIdError` if no episode is found with the given Id


        Given a valid episode Id the corresponding episode data is fetched and
        the :class:`Episode()` instance is returned.

        .. Note:: When the :class:`Episode()` is loaded using :func:`get_episode()`
            the episode attribute will be None.

        Example::

            >>> from pytvdbapi import api
            >>> db = api.TVDB("B43FF87DE395DF56")
            >>> episode = db.get_episode(308834, "en")
            >>> episode.id
            308834

            >>> episode.EpisodeName
            'Crocodile'

        """

        logger.debug("Getting episode with id {0} with language {1}".format(episode_id, language))

        if language != 'all' and language not in __LANGUAGES__:
            raise error.TVDBValueError("{0} is not a valid language".format(language))

        context = {'episodeid': episode_id, "language": language,
                   'mirror': self.mirrors.get_mirror(TypeMask.XML).url,
                   'api_key': self.config['api_key']}

        url = __episode__.format(**context)

        try:
            data = self.loader.load(url, cache)
        except error.TVDBNotFoundError:
            raise error.TVDBIdError("No Episode with id {0} found".format(episode_id))
        except error.ConnectionError as _error:
            logger.debug("Unable to connect to URL: {0}. {1}".format(url, _error))
            raise

        if data.strip():
            data = generate_tree(data)
        else:
            raise error.TVDBIdError("No Episode with id {0} found".format(episode_id))

        episodes = parse_xml(data, "Episode")
        assert len(episodes) <= 1, "Should not find more than one episodes"

        if len(episodes) >= 1:
            return Episode(episodes[0], None, self.config)
        else:
            raise error.TVDBIdError("No Episode with id {0} found".format(episode_id))
Ejemplo n.º 8
0
class TVDB(object):
    """
    :param api_key: The API key to use to communicate with the server
    :param kwargs:

    This is the main entry point for the API. The functionality of the API is
    controlled by configuring the keyword arguments. The supported keyword
    arguments are:

    * *force_lang* (default=False). Deprecated in version 0.4. Using it will
      have no affect but will issue a warning in the log file.

    * *cache_dir* (default=/<system tmp dir>/pytvdbapi/). Specifies the
      directory to use for caching the server requests.

    .. versionadded:: 0.3

    * *actors* (default=False) The extended actor information is stored in a
      separate XML file and would require an additional request to the server
      to obtain. To limit the resource usage, the actor information will only
      be loaded when explicitly requested.

      .. note:: The :class:`Show()` object always contain a list of actor
        names.

    * *banners* (default=False) The extended banner information is stored in a
      separate XML file and would require an additional request to the server
      to obtain. To limit the resource usage, the banner information will only
      be loaded when explicitly requested.

    .. versionadded:: 0.4

    * *ignore_case* (default=False) If set to True, all attributes on the
      :class:`Show` and :class:`Episode` instances will be accessible in a
      case insensitive manner. If set to False, the default, all
      attributes will be case sensitive and retain the same casing
      as provided by `thetvdb.com <http://thetvdb.com>`_.
    """
    def __init__(self, api_key, **kwargs):
        self.config = dict()

        #cache old searches to avoid hitting the server
        self.search_buffer = dict()

        #Store the path to where we are
        self.path = os.path.abspath(os.path.dirname(__file__))

        if 'force_lang' in kwargs:
            logger.warning(
                "'force_lang' keyword argument is deprecated as of version 0.4"
            )

        #extract all argument and store for later use
        self.config['api_key'] = api_key
        self.config['cache_dir'] = kwargs.get(
            "cache_dir", os.path.join(tempfile.gettempdir(), name))
        self.config['actors'] = kwargs.get('actors', False)
        self.config['banners'] = kwargs.get('banners', False)
        self.config['ignore_case'] = kwargs.get('ignore_case', False)

        #Create the loader object to use
        self.loader = Loader(self.config['cache_dir'])

        #Create the list of available mirrors
        tree = generate_tree(
            self.loader.load(__mirrors__.format(**self.config)))
        self.mirrors = MirrorList(tree)

    def search(self, show, language, cache=True):
        """
        :param show: The show name to search for
        :param language: The language abbreviation to search for. E.g. "en"
        :param cache: If False, the local cache will not be used and the
            resources will be reloaded from server.
        :return: A :class:`Search()` instance
        :raise: :class:`pytvdbapi.error.TVDBValueError`

        Searches the server for a show with the provided show name in the
        provided language. The language should be one of the supported
        language abbreviations or it could be set to *all* to search all
        languages. It will raise :class:`pytvdbapi.error.TVDBValueError` if
        an invalid language is provided.

        Searches are always cached within a session to make subsequent
        searches with the same parameters really cheap and fast. If *cache*
        is set to True searches will also be cached across sessions,
        this is recommended to increase speed and to reduce the workload of
        the servers.

        Example::

            >>> from pytvdbapi import api
            >>> db = api.TVDB("B43FF87DE395DF56")
            >>> search = db.search("Dexter", "en")
            >>> for s in search:
            ...     print(s)
            ...
            <Show - Dexter>
        """
        logger.debug("Searching for {0} using language {1}".format(
            show, language))

        if language != 'all' and language not in __LANGUAGES__:
            raise error.TVDBValueError(
                "{0} is not a valid language".format(language))

        if (show, language) not in self.search_buffer or not cache:
            if sys.version_info < (3, 0):
                show = str(show.encode('utf-8'))

            context = {'series': quote(show), "language": language}
            data = generate_tree(
                self.loader.load(__search__.format(**context), cache))
            shows = [
                Show(d, self, language, self.config)
                for d in parse_xml(data, "Series")
            ]

            self.search_buffer[(show, language)] = shows

        return Search(self.search_buffer[(show, language)], show, language)

    def get(self, series_id, language, cache=True):
        """
        .. versionadded:: 0.3
        .. deprecated:: 0.4 Use :func:`get_series` instead.

        :param series_id: The Show Id to fetch
        :param language: The language abbreviation to search for. E.g. "en"
        :param cache: If False, the local cache will not be used and the
                    resources will be reloaded from server.

        :return: A :class:`Show()` instance
        :raise: :class:`pytvdbapi.error.TVDBValueError`, :class:`pytvdbapi.error.TVDBIdError`

        """

        logger.warning(
            "Using deprecated function 'get'. Use 'get_series' instead")
        return self.get_series(series_id, language, cache)

    def get_series(self, series_id, language, cache=True):
        """
        .. versionadded:: 0.4

        :param series_id: The Show Id to fetch
        :param language: The language abbreviation to search for. E.g. "en"
        :param cache: If False, the local cache will not be used and the
                    resources will be reloaded from server.

        :return: A :class:`Show()` instance
        :raise: :class:`pytvdbapi.error.TVDBValueError`, :class:`pytvdbapi.error.TVDBIdError`

        Provided a valid Show ID, the data for the show is fetched and a
        corresponding :class:`Show()` object is returned.

        Example::

            >>> from pytvdbapi import api
            >>> db = api.TVDB("B43FF87DE395DF56")
            >>> show = db.get( 79349, "en" )
            >>> show.id
            79349

            >>> show.SeriesName
            'Dexter'

        """

        logger.debug("Getting series with id {0} with language {1}".format(
            series_id, language))

        if language != 'all' and language not in __LANGUAGES__:
            raise error.TVDBValueError(
                "{0} is not a valid language".format(language))

        context = {
            'seriesid': series_id,
            "language": language,
            'mirror': self.mirrors.get_mirror(TypeMask.XML).url,
            'api_key': self.config['api_key']
        }

        url = __series__.format(**context)
        try:
            data = self.loader.load(url, cache)
        except error.TVDBNotFoundError:
            raise error.TVDBIdError(
                "Series id {0} not found".format(series_id))
        except error.ConnectionError as _error:
            logger.debug("Unable to connect to URL: {0}. {1}".format(
                url, _error))
            raise

        if data.strip():
            data = generate_tree(data)
        else:
            raise error.TVDBIdError(
                "No Show with id {0} found".format(series_id))

        series = parse_xml(data, "Series")
        assert len(series) <= 1, "Should not find more than one series"

        if len(series) >= 1:
            return Show(series[0], self, language, self.config)
        else:
            raise error.TVDBIdError(
                "No Show with id {0} found".format(series_id))

    def get_episode(self, episode_id, language, cache=True):
        """
        .. versionadded:: 0.4

        :param episode_id: The Episode Id to fetch
        :param language: The language abbreviation to search for. E.g. "en"
        :param cache: If False, the local cache will not be used and the
                    resources will be reloaded from server.

        :return: An :class:`Episode()` instance
        :raise: :class:`pytvdbapi.error.TVDBIdError` if no episode is found with the given Id


        Given a valid episode Id the corresponding episode data is fetched and
        the :class:`Episode()` instance is returned.

        .. Note:: When the :class:`Episode()` is loaded using :func:`get_episode()`
            the episode attribute will be None.

        Example::

            >>> from pytvdbapi import api
            >>> db = api.TVDB("B43FF87DE395DF56")
            >>> episode = db.get_episode(308834, "en")
            >>> episode.id
            308834

            >>> episode.EpisodeName
            'Crocodile'

        """

        logger.debug("Getting episode with id {0} with language {1}".format(
            episode_id, language))

        if language != 'all' and language not in __LANGUAGES__:
            raise error.TVDBValueError(
                "{0} is not a valid language".format(language))

        context = {
            'episodeid': episode_id,
            "language": language,
            'mirror': self.mirrors.get_mirror(TypeMask.XML).url,
            'api_key': self.config['api_key']
        }

        url = __episode__.format(**context)

        try:
            data = self.loader.load(url, cache)
        except error.TVDBNotFoundError:
            raise error.TVDBIdError(
                "No Episode with id {0} found".format(episode_id))
        except error.ConnectionError as _error:
            logger.debug("Unable to connect to URL: {0}. {1}".format(
                url, _error))
            raise

        if data.strip():
            data = generate_tree(data)
        else:
            raise error.TVDBIdError(
                "No Episode with id {0} found".format(episode_id))

        episodes = parse_xml(data, "Episode")
        assert len(episodes) <= 1, "Should not find more than one episodes"

        if len(episodes) >= 1:
            return Episode(episodes[0], None, self.config)
        else:
            raise error.TVDBIdError(
                "No Episode with id {0} found".format(episode_id))
Ejemplo n.º 9
0
class TVDB(object):
    """
    :param api_key: The API key to use to communicate with the server
    :param kwargs:

    This is the main entry point for the API. The functionality of the API is
    controlled by configuring the keyword arguments. The supported keyword
    arguments are:

    * **cache_dir** (default=/<system tmp dir>/pytvdbapi/). Specifies the
      directory to use for caching the server requests.

    .. versionadded:: 0.3

    * **actors** (default=False) The extended actor information is stored in a
      separate XML file and would require an additional request to the server
      to obtain. To limit the resource usage, the actor information will only
      be loaded when explicitly requested.

      .. note:: The :class:`Show()` object always contain a list of actor
        names.

    * **banners** (default=False) The extended banner information is stored in a
      separate XML file and would require an additional request to the server
      to obtain. To limit the resource usage, the banner information will only
      be loaded when explicitly requested.

    .. versionadded:: 0.4

    * **ignore_case** (default=False) If set to True, all attributes on the
      :class:`Show` and :class:`Episode` instances will be accessible in a
      case insensitive manner. If set to False, the default, all
      attributes will be case sensitive and retain the same casing
      as provided by `thetvdb.com <http://thetvdb.com>`_.

    * **timeout** (default=None) When set to a number, will cause the http
    request to timeout after that number of seconds.

    """
    @unicode_arguments
    def __init__(self, api_key, **kwargs):
        self.config = dict()

        # cache old searches to avoid hitting the server
        self.search_buffer = dict()

        # extract all argument and store for later use
        self.config['api_key'] = api_key
        self.config['cache_dir'] = kwargs.get(
            "cache_dir",
            make_unicode(os.path.join(tempfile.gettempdir(), __NAME__)))

        self.config['actors'] = kwargs.get('actors', False)
        self.config['banners'] = kwargs.get('banners', False)
        self.config['ignore_case'] = kwargs.get('ignore_case', False)

        # Create the loader object to use
        self.loader = Loader(self.config['cache_dir'],
                             timeout=kwargs.get('timeout', None))

        # Create the list of available mirrors
        tree = generate_tree(self.loader.load(mirrors.format(**self.config)))
        self.mirrors = MirrorList(tree)

    @unicode_arguments
    def search(self, show, language, cache=True):
        """
        :param show: The show name to search for
        :param language: The language abbreviation to search for. E.g. "en"
        :param cache: If False, the local cache will not be used and the
            resources will be reloaded from server.
        :return: A :class:`Search()` instance
        :raise: :exc:`pytvdbapi.error.TVDBValueError`

        Searches the server for a show with the provided show name in the
        provided language. The language should be one of the supported
        language abbreviations or it could be set to *all* to search all
        languages. It will raise :class:`pytvdbapi.error.TVDBValueError` if
        an invalid language is provided.

        Searches are always cached within a session to make subsequent
        searches with the same parameters fast. If *cache*
        is set to True searches will also be cached across sessions,
        this is recommended to increase speed and to reduce the workload of
        the servers.

        Example::

            >>> from pytvdbapi import api
            >>> db = api.TVDB("B43FF87DE395DF56")
            >>> result = db.search("House", "en")

            >>> print(result[0])
            <Show - House>

            >>> for show in result:
            ...     print(show) # doctest: +ELLIPSIS
            <Show - House>
            ...
        """

        logger.debug(u"Searching for {0} using language {1}".format(
            show, language))

        if language != u'all' and language not in __LANGUAGES__:
            raise error.TVDBValueError(
                u"{0} is not a valid language".format(language))

        if (show, language) not in self.search_buffer or not cache:
            context = {'series': quote(make_bytes(show)), "language": language}
            data = generate_tree(
                self.loader.load(search.format(**context), cache))
            shows = [
                Show(d, self, language, self.config)
                for d in parse_xml(data, "Series")
            ]

            self.search_buffer[(show, language)] = shows

        return Search(self.search_buffer[(show, language)], show, language)

    @unicode_arguments
    def get_series(self, series_id, language, id_type='tvdb', cache=True):
        """
        .. versionadded:: 0.4
        .. versionchanged:: 0.5 Added *id_type* parameter

        :param series_id: The Show Id to fetch
        :param language: The language abbreviation to search for. E.g. "en"
        :param id_type: Information about what kind of id is provided. Should be one of *('tvdb', 'imdb',
            'zap2it')*
        :param cache: If False, the local cache will not be used and the
                    resources will be reloaded from server.

        :return: A :class:`Show()` instance
        :raise: :exc:`pytvdbapi.error.TVDBValueError`, :exc:`pytvdbapi.error.TVDBIdError`

        Provided a valid Show ID, the data for the show is fetched and a
        corresponding :class:`Show()` object is returned.

        Example::

            >>> from pytvdbapi import api
            >>> db = api.TVDB("B43FF87DE395DF56")
            >>> show = db.get_series( 79349, "en" )  # Load Dexter
            >>> print(show.SeriesName)
            Dexter
        """
        if id_type not in ('tvdb', 'imdb', 'zap2it'):
            raise error.TVDBValueError("Invalid id type")
        elif language != 'all' and language not in __LANGUAGES__:
            raise error.TVDBValueError(
                u"{0} is not a valid language".format(language))

        # Map id type to url template
        __url__ = {'tvdb': series, 'imdb': imdbid, 'zap2it': zap2itid}

        try:
            series_id = text_type(series_id)
        except ValueError:
            raise error.TVDBValueError(
                "Invalid id type, expected {0} or {1}, got {2}".format(
                    text_type, int_types, type(series_id)))

        if id_type == 'imdb':
            series_id = series_id[2:] if series_id.startswith(
                'tt') else series_id
        elif id_type == 'zap2it':
            series_id = series_id if series_id.startswith(
                'EP') else u'EP' + series_id.rjust(8, '0')

        logger.debug(
            u"Getting series with id {0}({2}) with language {1}".format(
                series_id, language, id_type))

        context = {
            'seriesid': series_id,
            "language": language,
            'mirror': self.mirrors.get_mirror(TypeMask.XML).url,
            'api_key': self.config['api_key'],
            'imdbid': series_id,
            'zap2itid': series_id
        }

        url = __url__[id_type].format(**context)
        logger.debug(u'Getting series from {0}'.format(url))

        try:
            data = self.loader.load(url, cache)
        except error.TVDBNotFoundError:
            raise error.TVDBIdError(
                u"Series id {0} not found".format(series_id))

        data = generate_tree(data)

        series_data = parse_xml(data, "Series")

        if len(series) == 0:
            raise error.BadData("Bad data received")
        else:
            return Show(series_data[0], self, language, self.config, data)

    @unicode_arguments
    @deprecate_episode_id
    def get_episode(self, language, method="id", cache=True, **kwargs):
        """
        .. versionadded:: 0.4
        .. versionchanged:: 0.5 Added the possibility to get an episode using *default*, *dvd*, and *absolute*
            sort order

        :param episode_id: *Deprecated in 0.5* Use the *episodeid* keyword argument with the *id*
            method instead
        :param language: The language abbreviation to search for. E.g. "en"
        :param cache: If False, the local cache will not be used and the
                    resources will be reloaded from server.
        :param method: (default=id) Specify what method should be used to get the episode. Depending on
            what method is specified, different parameters must be passed as keyword arguments. Should be one
            of (id, default, dvd, absolute).
        :param kwargs: *episodeid*, *seriesid*, *seasonnumber*, *episodenumber* and *absolutenumber*. See
            the examples for information on how to use them.
        :return: An :class:`Episode()` instance
        :raise: :exc:`pytvdbapi.error.TVDBValueError`, :exc:`pytvdbapi.error.BadData`

        Retrieves a single episode. Depending on what method is specified different criteria can be used to
        retrieve the episode.

        Examples:

        Load an episode using the episode id

        >>> from pytvdbapi import api
        >>> db = api.TVDB("B43FF87DE395DF56")
        >>> ep = db.get_episode("en", episodeid=308834)  # id is the default method
        >>> print(ep.EpisodeName)
        Crocodile

        Load an episode using dvd and default sort order

        >>> ep = db.get_episode("en", "dvd", seasonnumber=2, episodenumber=5, seriesid=79349)
        >>> print(ep.EpisodeName)
        The Dark Defender

        >>> ep = db.get_episode("en", "default", seasonnumber=2, episodenumber=6, seriesid=79349)
        >>> print(ep.EpisodeName)
        Dex, Lies, and Videotape

        Load an episode using the absolute number

        >>> ep = db.get_episode("en", "absolute", absolutenumber=19, seriesid=79349)
        >>> print(ep.EpisodeName)
        That Night, A Forest Grew

        Under some circumstances the backend server fails to return a proper **404** file not found error
        response when the requested episode can not be found, but instead returns a valid HTML file with
        the content *404 file not found*. For this reason it is required to check for both
        :exc:`pytvdbapi.error.TVDBValueError` and :exc:`pytvdbapi.error.BadData` to detect an issue
        downloading the episode.

        >>> from pytvdbapi.error import BadData
        >>> from pytvdbapi.error import TVDBNotFoundError
        >>> try:
        ...    ep = db.get_episode("en", episodeid=308834)
        ... except TVDBNotFoundError:
        ...    # this is the standard 404 error code returned from the server
        ...    pass
        ... except BadData:
        ...     # This is when the server returns a 200 code but with a HTML page saying 404 Nothing found
        ...     pass

        .. Note:: When the :class:`Episode()` is loaded using :func:`get_episode()`
            the *season* attribute used to link the episode with a season will be None.
        """
        methods = {
            "default": default_order,
            "dvd": dvd_order,
            "absolute": absolute_order,
            "id": episode
        }

        if language != 'all' and language not in __LANGUAGES__:
            raise error.TVDBValueError(
                u"{0} is not a valid language".format(language))

        context = {
            "language": language,
            'mirror': self.mirrors.get_mirror(TypeMask.XML).url,
            'api_key': self.config['api_key']
        }

        kwargs.update(context)

        try:
            url = methods[method]
        except KeyError:
            raise error.TVDBValueError(
                u"{0} is not a valid get method".format(method))

        try:
            url = url.format(**kwargs)
        except KeyError:
            raise error.TVDBValueError(
                "Missing arguments for method {0}".format(method))

        logger.debug(u'Getting episode from {0}'.format(url))

        data = self.loader.load(url, cache)
        data = generate_tree(data)

        episodes = parse_xml(data, "Episode")

        if len(episodes) == 0:
            raise error.BadData("Bad data received")
        else:
            return Episode(episodes[0], None, self.config)

    @unicode_arguments
    def get_episode_by_air_date(self,
                                series_id,
                                language,
                                air_date,
                                cache=True):
        """
        .. versionadded:: 0.5

        :param series_id: The TVDB series id of the episode
        :param language: The language to search for. Should be a two letter abbreviation e.g. *en*.
        :param air_date: The air date to search for. Should be of type :class:`datetime.date`
        :type air_date: datetime.date
        :param cache: If False, the local cache will not be used and the
                    resources will be reloaded from server.

        :return: If found, an :class:`Episode` instance
        :raise: :exc:`pytvdbapi.error.TVDBValueError`


        .. Note:: When the :class:`Episode()` is loaded using :func:`get_episode_by_air_date`
            the *season* attribute used to link the episode with a season will be None.
        """
        if type(air_date) not in (datetime.date, ):
            raise error.TVDBValueError(
                "air_date should be of type datetime.date")
        elif language != 'all' and language not in __LANGUAGES__:
            raise error.TVDBValueError(
                u"{0} is not a valid language".format(language))

        context = {
            'seriesid': series_id,
            'airdate': air_date,
            "language": language,
            'mirror': self.mirrors.get_mirror(TypeMask.XML).url,
            'api_key': self.config['api_key']
        }

        url = airdate.format(**context)
        logger.debug(u'Getting episode from {0}'.format(url))

        data = self.loader.load(url, cache)

        data = generate_tree(data)

        # The xml has an "Error" element in it if no episode was found
        if has_element(data, 'Error'):
            raise error.TVDBNotFoundError(u"".format())

        episodes = parse_xml(data, "Episode")

        if len(episodes) == 0:
            raise error.BadData("Bad data received")
        else:
            return Episode(episodes[0], None, self.config)
Ejemplo n.º 10
0
class TVDB(object):
    """
    :param api_key: The API key to use to communicate with the server
    :param kwargs:

    This is the main entry point for the API. The functionality of the API is
    controlled by configuring the key word arguments. The supported key word
    arguments are:

    * *force_lang* (default=False). If set to True, the API will reload the
      language list from the server. If False, the local preloaded file
      will be used. The language list is relative stable but if there are
      changes it could be useful to set this to True to obtain a new version
      from the server. It is only necessary to do this once since the API
      stores the reloaded data for future use.

    * *cache_dir* (default=/<system tmp dir>/pytvdbapi/). Specifies the
      directory to use for caching the server requests.

    .. versionadded:: 0.3

    * *actors* (default=False) The extended actor information is stored in a
      separate XML file and would require an additional request to the server
      to obtain. To limit the resource usage, the actor information will only
      be loaded when explicitly requested.

      .. note:: The :class:`Show()` object always contain a list of actor
        names.

    * *banners* (default=False) The extended banner information is stored in a
      separate XML file and would require an additional request to the server
      to obtain. To limit the resource usage, the banner information will only
      be loaded when explicitly requested.
    """

    def __init__(self, api_key, **kwargs):
        self.config = dict()

        #cache old searches to avoid hitting the server
        self.search_buffer = dict()

        #Store the path to where we are
        self.path = os.path.abspath(os.path.dirname(__file__))

        #extract all argument and store for later use
        self.config['force_lang'] = kwargs.get("force_lang", False)
        self.config['api_key'] = api_key
        self.config['cache_dir'] = kwargs.get("cache_dir",
            os.path.join(tempfile.gettempdir(), name))
        self.config['actors'] = kwargs.get('actors', False)
        self.config['banners'] = kwargs.get('banners', False)

        #Create the loader object to use
        self.loader = Loader(self.config['cache_dir'])

        language_file = resource_filename(__name__, 'data/languages.xml')

        #If requested, update the local language file from the server
        if self.config['force_lang']:
            logger.debug("updating Language file from server")
            with open(language_file, "wt") as languages:
                url = config.get("urls", "languages", raw=True)
                language_data = self.loader.load(url % self.config)
                languages.write(language_data)

        #Setup the list of supported languages
        with open(language_file, "rt") as languages:
            self.languages = LanguageList(generate_tree(languages.read()))

        #Create the list of available mirrors
        url = config.get("urls", "mirrors", raw=True)
        tree = generate_tree(self.loader.load(url % self.config))
        self.mirrors = MirrorList(tree)

    def search(self, show, language, cache=True):
        """
        :param show: The show name to search for
        :param language: The language abbreviation to search for. E.g. "en"
        :param cache: If False, the local cache will not be used and the
            resources will be reloaded from server.
        :return: A :class:`Search()` instance
        :raise: TVDBValueError

        Searches the server for a show with the provided show name in the
        provided language. The language should be one of the supported
        language abbreviations or it could be set to *all* to search all
        languages. It will raise :class:`TVDBValueError` if an invalid
        language is provided.

        Searches are always cached within a session to make subsequent
        searches with the same parameters really cheap and fast. If *cache*
        is set to True searches will also be cached across sessions,
        this is recommended to increase speed and to reduce the workload of
        the servers.

        Example::

            >>> from pytvdbapi import api
            >>> db = api.TVDB("B43FF87DE395DF56")
            >>> search = db.search("Dexter", "en")
            >>> for s in search:
            ...     print(s)
            ...
            <Show - Dexter>
            <Show - Cliff Dexter>

        """

        logger.debug("Searching for {0} using language {1}"
            .format(show, language))

        if language != 'all' and language not in self.languages:
            raise error.TVDBValueError(
                "{0} is not a valid language".format(language))

        if (show, language) not in self.search_buffer:
            if sys.version_info < (3, 0):
                show = str(show.encode('utf-8'))

            context = {'series': quote(show), "language": language}
            url = config.get("urls", "search", raw=True)
            data = generate_tree(self.loader.load(url % context, cache))
            shows = [Show(d, self, language)
                     for d in parse_xml(data, "Series")]

            self.search_buffer[(show, language)] = shows

        return Search(self.search_buffer[(show, language)], show, language)

    def get(self, series_id, language, cache=True):
        """
        .. versionadded:: 0.3

        :param series_id: The Show Id to fetch
        :param language: The language abbreviation to search for. E.g. "en"
        :param cache: If False, the local cache will not be used and the
                    resources will be reloaded from server.

        :return: A :class:`Show()` instance
        :raise: TVDBValueError, TVDBIdError

        Provided a valid Show ID, the data for the show is fetched and a
        corresponding :class:`Show()` object is returned.

        Example::

            >>> from pytvdbapi import api
            >>> db = api.TVDB("B43FF87DE395DF56")
            >>> show = db.get( 79349, "en" )
            >>> show.id
            79349
            >>> show.SeriesName
            'Dexter'
        """
        logger.debug("Getting show with id {0} with language {1}".format(
            series_id, language))

        if language != 'all' and language not in self.languages:
            raise error.TVDBValueError("{0} is not a valid language".format(
                language))

        context = {'seriesid': series_id, "language": language,
                   'mirror': self.mirrors.get_mirror(TypeMask.XML).url,
                   'api_key': self.config['api_key']}

        url = config.get("urls", "series", raw=True) % context

        try:
            data = self.loader.load(url, cache)
        except error.ConnectionError as _error:
            logger.debug("Unable to connect to URL: {0}. {1}".format(url,
                _error))
            raise error.TVDBIdError("No Show with id {0} found".format(
                series_id))

        if data.strip():
            data = generate_tree(data)
        else:
            logger.debug("Empty data received for id {0}".format(series_id))
            raise error.TVDBIdError("No Show with id {0} found".format(
                series_id))

        series = parse_xml(data, "Series")
        assert len(series) <= 1, "Should not find more than one series"

        if len(series) >= 1:
            return Show(series[0], self, language)
        else:
            raise error.TVDBIdError("No Show with id {0} found".format(
                series_id))

    def get_episode(self, episode_id, language, cache=True):
        logger.debug("Getting episode with id {0} with language {1}".format(
            episode_id, language))

        if language != 'all' and language not in self.languages:
            raise error.TVDBValueError("{0} is not a valid language".format(
                language))

        context = {'episodeid': episode_id, "language": language,
                   'mirror': self.mirrors.get_mirror(TypeMask.XML).url,
                   'api_key': self.config['api_key']}

        url = config.get("urls", "episode", raw=True) % context

        try:
            data = self.loader.load(url, cache)
        except error.ConnectionError as _error:
            logger.debug("Unable to connect to URL: {0}. {1}".format(url,
                _error))
            raise error.TVDBIdError("No Episode with id {0} found".format(
                episode_id))

        if data.strip():
            data = generate_tree(data)
        else:
            logger.debug("Empty data received for id {0}".format(episode_id))
            raise error.TVDBIdError("No Episode with id {0} found".format(
                episode_id))

        episodes = parse_xml(data, "Episode")
        assert len(episodes) <= 1, "Should not find more than one episodes"

        if len(episodes) >= 1:
            return Episode(episodes[0], None)
        else:
            raise error.TVDBIdError("No Episode with id {0} found".format(
                episode_id))