util.py 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  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. import warnings
  28. class FeedParserDict(dict):
  29. keymap = {
  30. 'channel': 'feed',
  31. 'items': 'entries',
  32. 'guid': 'id',
  33. 'date': 'updated',
  34. 'date_parsed': 'updated_parsed',
  35. 'description': ['summary', 'subtitle'],
  36. 'description_detail': ['summary_detail', 'subtitle_detail'],
  37. 'url': ['href'],
  38. 'modified': 'updated',
  39. 'modified_parsed': 'updated_parsed',
  40. 'issued': 'published',
  41. 'issued_parsed': 'published_parsed',
  42. 'copyright': 'rights',
  43. 'copyright_detail': 'rights_detail',
  44. 'tagline': 'subtitle',
  45. 'tagline_detail': 'subtitle_detail',
  46. }
  47. def __getitem__(self, key):
  48. """
  49. :return: A :class:`FeedParserDict`.
  50. """
  51. if key == 'category':
  52. try:
  53. return dict.__getitem__(self, 'tags')[0]['term']
  54. except IndexError:
  55. raise KeyError("object doesn't have key 'category'")
  56. elif key == 'enclosures':
  57. norel = lambda link: FeedParserDict([(name, value) for (name, value) in link.items() if name != 'rel'])
  58. return [
  59. norel(link)
  60. for link in dict.__getitem__(self, 'links')
  61. if link['rel'] == 'enclosure'
  62. ]
  63. elif key == 'license':
  64. for link in dict.__getitem__(self, 'links'):
  65. if link['rel'] == 'license' and 'href' in link:
  66. return link['href']
  67. elif key == 'updated':
  68. # Temporarily help developers out by keeping the old
  69. # broken behavior that was reported in issue 310.
  70. # This fix was proposed in issue 328.
  71. if (
  72. not dict.__contains__(self, 'updated')
  73. and dict.__contains__(self, 'published')
  74. ):
  75. warnings.warn(
  76. "To avoid breaking existing software while "
  77. "fixing issue 310, a temporary mapping has been created "
  78. "from `updated` to `published` if `updated` doesn't "
  79. "exist. This fallback will be removed in a future version "
  80. "of feedparser.",
  81. DeprecationWarning,
  82. )
  83. return dict.__getitem__(self, 'published')
  84. return dict.__getitem__(self, 'updated')
  85. elif key == 'updated_parsed':
  86. if (
  87. not dict.__contains__(self, 'updated_parsed')
  88. and dict.__contains__(self, 'published_parsed')
  89. ):
  90. warnings.warn(
  91. "To avoid breaking existing software while "
  92. "fixing issue 310, a temporary mapping has been created "
  93. "from `updated_parsed` to `published_parsed` if "
  94. "`updated_parsed` doesn't exist. This fallback will be "
  95. "removed in a future version of feedparser.",
  96. DeprecationWarning,
  97. )
  98. return dict.__getitem__(self, 'published_parsed')
  99. return dict.__getitem__(self, 'updated_parsed')
  100. else:
  101. realkey = self.keymap.get(key, key)
  102. if isinstance(realkey, list):
  103. for k in realkey:
  104. if dict.__contains__(self, k):
  105. return dict.__getitem__(self, k)
  106. elif dict.__contains__(self, realkey):
  107. return dict.__getitem__(self, realkey)
  108. return dict.__getitem__(self, key)
  109. def __contains__(self, key):
  110. if key in ('updated', 'updated_parsed'):
  111. # Temporarily help developers out by keeping the old
  112. # broken behavior that was reported in issue 310.
  113. # This fix was proposed in issue 328.
  114. return dict.__contains__(self, key)
  115. try:
  116. self.__getitem__(key)
  117. except KeyError:
  118. return False
  119. else:
  120. return True
  121. has_key = __contains__
  122. def get(self, key, default=None):
  123. """
  124. :return: A :class:`FeedParserDict`.
  125. """
  126. try:
  127. return self.__getitem__(key)
  128. except KeyError:
  129. return default
  130. def __setitem__(self, key, value):
  131. key = self.keymap.get(key, key)
  132. if isinstance(key, list):
  133. key = key[0]
  134. return dict.__setitem__(self, key, value)
  135. def setdefault(self, k, default):
  136. if k not in self:
  137. self[k] = default
  138. return default
  139. return self[k]
  140. def __getattr__(self, key):
  141. # __getattribute__() is called first; this will be called
  142. # only if an attribute was not already found
  143. try:
  144. return self.__getitem__(key)
  145. except KeyError:
  146. raise AttributeError("object has no attribute '%s'" % key)
  147. def __hash__(self):
  148. # This is incorrect behavior -- dictionaries shouldn't be hashable.
  149. # Note to self: remove this behavior in the future.
  150. return id(self)