answer.py 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. # SPDX-License-Identifier: AGPL-3.0-or-later
  2. """
  3. Typification of the *answer* results. Results of this type are rendered in
  4. the :origin:`answers.html <searx/templates/simple/elements/answers.html>`
  5. template.
  6. ----
  7. .. autoclass:: BaseAnswer
  8. :members:
  9. :show-inheritance:
  10. .. autoclass:: Answer
  11. :members:
  12. :show-inheritance:
  13. .. autoclass:: Translations
  14. :members:
  15. :show-inheritance:
  16. .. autoclass:: Weather
  17. :members:
  18. :show-inheritance:
  19. .. autoclass:: AnswerSet
  20. :members:
  21. :show-inheritance:
  22. """
  23. # pylint: disable=too-few-public-methods
  24. from __future__ import annotations
  25. __all__ = ["AnswerSet", "Answer", "Translations", "Weather"]
  26. import msgspec
  27. from ._base import Result
  28. class BaseAnswer(Result, kw_only=True):
  29. """Base class of all answer types. It is not intended to build instances of
  30. this class (aka *abstract*)."""
  31. class AnswerSet:
  32. """Aggregator for :py:obj:`BaseAnswer` items in a result container."""
  33. def __init__(self):
  34. self._answerlist = []
  35. def __len__(self):
  36. return len(self._answerlist)
  37. def __bool__(self):
  38. return bool(self._answerlist)
  39. def add(self, answer: BaseAnswer) -> None:
  40. a_hash = hash(answer)
  41. for i in self._answerlist:
  42. if hash(i) == a_hash:
  43. return
  44. self._answerlist.append(answer)
  45. def __iter__(self):
  46. """Sort items in this set and iterate over the items."""
  47. self._answerlist.sort(key=lambda answer: answer.template)
  48. yield from self._answerlist
  49. def __contains__(self, answer: BaseAnswer) -> bool:
  50. a_hash = hash(answer)
  51. for i in self._answerlist:
  52. if hash(i) == a_hash:
  53. return True
  54. return False
  55. class Answer(BaseAnswer, kw_only=True):
  56. """Simple answer type where the *answer* is a simple string with an optional
  57. :py:obj:`url field <Result.url>` field to link a resource (article, map, ..)
  58. related to the answer."""
  59. template: str = "answer/legacy.html"
  60. answer: str
  61. """Text of the answer."""
  62. def __hash__(self):
  63. """The hash value of field *answer* is the hash value of the
  64. :py:obj:`Answer` object. :py:obj:`Answer <Result.__eq__>` objects are
  65. equal, when the hash values of both objects are equal."""
  66. return hash(self.answer)
  67. class Translations(BaseAnswer, kw_only=True):
  68. """Answer type with a list of translations.
  69. The items in the list of :py:obj:`Translations.translations` are of type
  70. :py:obj:`Translations.Item`:
  71. .. code:: python
  72. def response(resp):
  73. results = []
  74. ...
  75. foo_1 = Translations.Item(
  76. text="foobar",
  77. synonyms=["bar", "foo"],
  78. examples=["foo and bar are placeholders"],
  79. )
  80. foo_url="https://www.deepl.com/de/translator#en/de/foo"
  81. ...
  82. Translations(results=results, translations=[foo], url=foo_url)
  83. """
  84. template: str = "answer/translations.html"
  85. """The template in :origin:`answer/translations.html
  86. <searx/templates/simple/answer/translations.html>`"""
  87. translations: list[Translations.Item]
  88. """List of translations."""
  89. def __post_init__(self):
  90. if not self.translations:
  91. raise ValueError("Translation does not have an item in the list translations")
  92. class Item(msgspec.Struct, kw_only=True):
  93. """A single element of the translations / a translation. A translation
  94. consists of at least a mandatory ``text`` property (the translation) ,
  95. optional properties such as *definitions*, *synonyms* and *examples* are
  96. possible."""
  97. text: str
  98. """Translated text."""
  99. transliteration: str = ""
  100. """Transliteration_ of the requested translation.
  101. .. _Transliteration: https://en.wikipedia.org/wiki/Transliteration
  102. """
  103. examples: list[str] = []
  104. """List of examples for the requested translation."""
  105. definitions: list[str] = []
  106. """List of definitions for the requested translation."""
  107. synonyms: list[str] = []
  108. """List of synonyms for the requested translation."""
  109. class Weather(BaseAnswer, kw_only=True):
  110. """Answer type for weather data."""
  111. template: str = "answer/weather.html"
  112. """The template is located at :origin:`answer/weather.html
  113. <searx/templates/simple/answer/weather.html>`"""
  114. location: str
  115. """The geo-location the weather data is from (e.g. `Berlin, Germany`)."""
  116. current: Weather.DataItem
  117. """Current weather at ``location``."""
  118. forecasts: list[Weather.DataItem] = []
  119. """Weather forecasts for ``location``."""
  120. def __post_init__(self):
  121. if not self.location:
  122. raise ValueError("Weather answer is missing a location")
  123. class DataItem(msgspec.Struct, kw_only=True):
  124. """A container for weather data such as temperature, humidity, ..."""
  125. time: str | None = None
  126. """Time of the forecast - not needed for the current weather."""
  127. condition: str
  128. """Weather condition, e.g. `cloudy`, `rainy`, `sunny` ..."""
  129. temperature: str
  130. """Temperature string, e.g. `17°C`"""
  131. feelsLike: str | None = None
  132. """Felt temperature string, should be formatted like ``temperature``"""
  133. humidity: str | None = None
  134. """Humidity percentage string, e.g. `60%`"""
  135. pressure: str | None = None
  136. """Pressure string, e.g. `1030hPa`"""
  137. wind: str | None = None
  138. """Information about the wind, e.g. `W, 231°, 10 m/s`"""
  139. attributes: dict[str] = []
  140. """Key-Value dict of additional weather attributes that are not available above"""