Format: text/x-python
⬇ Download File (6.3 KB)
   1  # Copyright 2010-2023 Kurt McKee <contactme@kurtmckee.org>
   2  # Copyright 2002-2008 Mark Pilgrim
   3  # All rights reserved.
   4  #
   5  # This file is a part of feedparser.
   6  #
   7  # Redistribution and use in source and binary forms, with or without
   8  # modification, are permitted provided that the following conditions are met:
   9  #
  10  # * Redistributions of source code must retain the above copyright notice,
  11  #   this list of conditions and the following disclaimer.
  12  # * Redistributions in binary form must reproduce the above copyright notice,
  13  #   this list of conditions and the following disclaimer in the documentation
  14  #   and/or other materials provided with the distribution.
  15  #
  16  # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS IS'
  17  # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  18  # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  19  # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
  20  # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  21  # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  22  # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  23  # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  24  # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  25  # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  26  # POSSIBILITY OF SUCH DAMAGE.
  27  
  28  import warnings
  29  
  30  
  31  class FeedParserDict(dict):
  32      keymap = {
  33          'channel': 'feed',
  34          'items': 'entries',
  35          'guid': 'id',
  36          'date': 'updated',
  37          'date_parsed': 'updated_parsed',
  38          'description': ['summary', 'subtitle'],
  39          'description_detail': ['summary_detail', 'subtitle_detail'],
  40          'url': ['href'],
  41          'modified': 'updated',
  42          'modified_parsed': 'updated_parsed',
  43          'issued': 'published',
  44          'issued_parsed': 'published_parsed',
  45          'copyright': 'rights',
  46          'copyright_detail': 'rights_detail',
  47          'tagline': 'subtitle',
  48          'tagline_detail': 'subtitle_detail',
  49      }
  50  
  51      def __getitem__(self, key):
  52          """
  53          :return: A :class:`FeedParserDict`.
  54          """
  55  
  56          if key == 'category':
  57              try:
  58                  return dict.__getitem__(self, 'tags')[0]['term']
  59              except IndexError:
  60                  raise KeyError("object doesn't have key 'category'")
  61          elif key == 'enclosures':
  62              norel = lambda link: FeedParserDict([(name, value) for (name, value) in link.items() if name != 'rel'])
  63              return [
  64                  norel(link)
  65                  for link in dict.__getitem__(self, 'links')
  66                  if link['rel'] == 'enclosure'
  67              ]
  68          elif key == 'license':
  69              for link in dict.__getitem__(self, 'links'):
  70                  if link['rel'] == 'license' and 'href' in link:
  71                      return link['href']
  72          elif key == 'updated':
  73              # Temporarily help developers out by keeping the old
  74              # broken behavior that was reported in issue 310.
  75              # This fix was proposed in issue 328.
  76              if (
  77                      not dict.__contains__(self, 'updated')
  78                      and dict.__contains__(self, 'published')
  79              ):
  80                  warnings.warn(
  81                      "To avoid breaking existing software while "
  82                      "fixing issue 310, a temporary mapping has been created "
  83                      "from `updated` to `published` if `updated` doesn't "
  84                      "exist. This fallback will be removed in a future version "
  85                      "of feedparser.",
  86                      DeprecationWarning,
  87                  )
  88                  return dict.__getitem__(self, 'published')
  89              return dict.__getitem__(self, 'updated')
  90          elif key == 'updated_parsed':
  91              if (
  92                      not dict.__contains__(self, 'updated_parsed')
  93                      and dict.__contains__(self, 'published_parsed')
  94              ):
  95                  warnings.warn(
  96                      "To avoid breaking existing software while "
  97                      "fixing issue 310, a temporary mapping has been created "
  98                      "from `updated_parsed` to `published_parsed` if "
  99                      "`updated_parsed` doesn't exist. This fallback will be "
 100                      "removed in a future version of feedparser.",
 101                      DeprecationWarning,
 102                  )
 103                  return dict.__getitem__(self, 'published_parsed')
 104              return dict.__getitem__(self, 'updated_parsed')
 105          else:
 106              realkey = self.keymap.get(key, key)
 107              if isinstance(realkey, list):
 108                  for k in realkey:
 109                      if dict.__contains__(self, k):
 110                          return dict.__getitem__(self, k)
 111              elif dict.__contains__(self, realkey):
 112                  return dict.__getitem__(self, realkey)
 113          return dict.__getitem__(self, key)
 114  
 115      def __contains__(self, key):
 116          if key in ('updated', 'updated_parsed'):
 117              # Temporarily help developers out by keeping the old
 118              # broken behavior that was reported in issue 310.
 119              # This fix was proposed in issue 328.
 120              return dict.__contains__(self, key)
 121          try:
 122              self.__getitem__(key)
 123          except KeyError:
 124              return False
 125          else:
 126              return True
 127  
 128      has_key = __contains__
 129  
 130      def get(self, key, default=None):
 131          """
 132          :return: A :class:`FeedParserDict`.
 133          """
 134  
 135          try:
 136              return self.__getitem__(key)
 137          except KeyError:
 138              return default
 139  
 140      def __setitem__(self, key, value):
 141          key = self.keymap.get(key, key)
 142          if isinstance(key, list):
 143              key = key[0]
 144          return dict.__setitem__(self, key, value)
 145  
 146      def setdefault(self, k, default):
 147          if k not in self:
 148              self[k] = default
 149              return default
 150          return self[k]
 151  
 152      def __getattr__(self, key):
 153          # __getattribute__() is called first; this will be called
 154          # only if an attribute was not already found
 155          try:
 156              return self.__getitem__(key)
 157          except KeyError:
 158              raise AttributeError("object has no attribute '%s'" % key)
 159  
 160      def __hash__(self):
 161          # This is incorrect behavior -- dictionaries shouldn't be hashable.
 162          # Note to self: remove this behavior in the future.
 163          return id(self)
 164