webscout 8.2.8__py3-none-any.whl → 8.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of webscout might be problematic. Click here for more details.

Files changed (197) hide show
  1. webscout/AIauto.py +34 -16
  2. webscout/AIbase.py +96 -37
  3. webscout/AIutel.py +491 -87
  4. webscout/Bard.py +441 -323
  5. webscout/Extra/GitToolkit/__init__.py +10 -10
  6. webscout/Extra/YTToolkit/ytapi/video.py +232 -232
  7. webscout/Litlogger/README.md +10 -0
  8. webscout/Litlogger/__init__.py +7 -59
  9. webscout/Litlogger/formats.py +4 -0
  10. webscout/Litlogger/handlers.py +103 -0
  11. webscout/Litlogger/levels.py +13 -0
  12. webscout/Litlogger/logger.py +92 -0
  13. webscout/Provider/AISEARCH/Perplexity.py +332 -358
  14. webscout/Provider/AISEARCH/felo_search.py +9 -35
  15. webscout/Provider/AISEARCH/genspark_search.py +30 -56
  16. webscout/Provider/AISEARCH/hika_search.py +4 -16
  17. webscout/Provider/AISEARCH/iask_search.py +410 -436
  18. webscout/Provider/AISEARCH/monica_search.py +4 -30
  19. webscout/Provider/AISEARCH/scira_search.py +6 -32
  20. webscout/Provider/AISEARCH/webpilotai_search.py +38 -64
  21. webscout/Provider/Blackboxai.py +155 -35
  22. webscout/Provider/ChatSandbox.py +2 -1
  23. webscout/Provider/Deepinfra.py +339 -339
  24. webscout/Provider/ExaChat.py +358 -358
  25. webscout/Provider/Gemini.py +169 -169
  26. webscout/Provider/GithubChat.py +1 -2
  27. webscout/Provider/Glider.py +3 -3
  28. webscout/Provider/HeckAI.py +172 -82
  29. webscout/Provider/LambdaChat.py +1 -0
  30. webscout/Provider/MCPCore.py +7 -3
  31. webscout/Provider/OPENAI/BLACKBOXAI.py +421 -139
  32. webscout/Provider/OPENAI/Cloudflare.py +38 -21
  33. webscout/Provider/OPENAI/FalconH1.py +457 -0
  34. webscout/Provider/OPENAI/FreeGemini.py +35 -18
  35. webscout/Provider/OPENAI/NEMOTRON.py +34 -34
  36. webscout/Provider/OPENAI/PI.py +427 -0
  37. webscout/Provider/OPENAI/Qwen3.py +304 -0
  38. webscout/Provider/OPENAI/README.md +952 -1253
  39. webscout/Provider/OPENAI/TwoAI.py +374 -0
  40. webscout/Provider/OPENAI/__init__.py +7 -1
  41. webscout/Provider/OPENAI/ai4chat.py +73 -63
  42. webscout/Provider/OPENAI/api.py +869 -644
  43. webscout/Provider/OPENAI/base.py +2 -0
  44. webscout/Provider/OPENAI/c4ai.py +34 -13
  45. webscout/Provider/OPENAI/chatgpt.py +575 -556
  46. webscout/Provider/OPENAI/chatgptclone.py +512 -487
  47. webscout/Provider/OPENAI/chatsandbox.py +11 -6
  48. webscout/Provider/OPENAI/copilot.py +258 -0
  49. webscout/Provider/OPENAI/deepinfra.py +327 -318
  50. webscout/Provider/OPENAI/e2b.py +140 -104
  51. webscout/Provider/OPENAI/exaai.py +420 -411
  52. webscout/Provider/OPENAI/exachat.py +448 -443
  53. webscout/Provider/OPENAI/flowith.py +7 -3
  54. webscout/Provider/OPENAI/freeaichat.py +12 -8
  55. webscout/Provider/OPENAI/glider.py +15 -8
  56. webscout/Provider/OPENAI/groq.py +5 -2
  57. webscout/Provider/OPENAI/heckai.py +311 -307
  58. webscout/Provider/OPENAI/llmchatco.py +9 -7
  59. webscout/Provider/OPENAI/mcpcore.py +18 -9
  60. webscout/Provider/OPENAI/multichat.py +7 -5
  61. webscout/Provider/OPENAI/netwrck.py +16 -11
  62. webscout/Provider/OPENAI/oivscode.py +290 -0
  63. webscout/Provider/OPENAI/opkfc.py +507 -496
  64. webscout/Provider/OPENAI/pydantic_imports.py +172 -0
  65. webscout/Provider/OPENAI/scirachat.py +29 -17
  66. webscout/Provider/OPENAI/sonus.py +308 -303
  67. webscout/Provider/OPENAI/standardinput.py +442 -433
  68. webscout/Provider/OPENAI/textpollinations.py +18 -11
  69. webscout/Provider/OPENAI/toolbaz.py +419 -413
  70. webscout/Provider/OPENAI/typefully.py +17 -10
  71. webscout/Provider/OPENAI/typegpt.py +21 -11
  72. webscout/Provider/OPENAI/uncovrAI.py +477 -462
  73. webscout/Provider/OPENAI/utils.py +90 -79
  74. webscout/Provider/OPENAI/venice.py +435 -425
  75. webscout/Provider/OPENAI/wisecat.py +387 -381
  76. webscout/Provider/OPENAI/writecream.py +166 -163
  77. webscout/Provider/OPENAI/x0gpt.py +26 -37
  78. webscout/Provider/OPENAI/yep.py +384 -356
  79. webscout/Provider/PI.py +2 -1
  80. webscout/Provider/TTI/README.md +55 -101
  81. webscout/Provider/TTI/__init__.py +4 -9
  82. webscout/Provider/TTI/aiarta.py +365 -0
  83. webscout/Provider/TTI/artbit.py +0 -0
  84. webscout/Provider/TTI/base.py +64 -0
  85. webscout/Provider/TTI/fastflux.py +200 -0
  86. webscout/Provider/TTI/magicstudio.py +201 -0
  87. webscout/Provider/TTI/piclumen.py +203 -0
  88. webscout/Provider/TTI/pixelmuse.py +225 -0
  89. webscout/Provider/TTI/pollinations.py +221 -0
  90. webscout/Provider/TTI/utils.py +11 -0
  91. webscout/Provider/TTS/__init__.py +2 -1
  92. webscout/Provider/TTS/base.py +159 -159
  93. webscout/Provider/TTS/openai_fm.py +129 -0
  94. webscout/Provider/TextPollinationsAI.py +308 -308
  95. webscout/Provider/TwoAI.py +239 -44
  96. webscout/Provider/UNFINISHED/Youchat.py +330 -330
  97. webscout/Provider/UNFINISHED/puterjs.py +635 -0
  98. webscout/Provider/UNFINISHED/test_lmarena.py +119 -119
  99. webscout/Provider/Writecream.py +246 -246
  100. webscout/Provider/__init__.py +2 -2
  101. webscout/Provider/ai4chat.py +33 -8
  102. webscout/Provider/granite.py +41 -6
  103. webscout/Provider/koala.py +169 -169
  104. webscout/Provider/oivscode.py +309 -0
  105. webscout/Provider/samurai.py +3 -2
  106. webscout/Provider/scnet.py +1 -0
  107. webscout/Provider/typegpt.py +3 -3
  108. webscout/Provider/uncovr.py +368 -368
  109. webscout/client.py +70 -0
  110. webscout/litprinter/__init__.py +58 -58
  111. webscout/optimizers.py +419 -419
  112. webscout/scout/README.md +3 -1
  113. webscout/scout/core/crawler.py +134 -64
  114. webscout/scout/core/scout.py +148 -109
  115. webscout/scout/element.py +106 -88
  116. webscout/swiftcli/Readme.md +323 -323
  117. webscout/swiftcli/plugins/manager.py +9 -2
  118. webscout/version.py +1 -1
  119. webscout/zeroart/__init__.py +134 -134
  120. webscout/zeroart/effects.py +100 -100
  121. webscout/zeroart/fonts.py +1238 -1238
  122. {webscout-8.2.8.dist-info → webscout-8.3.dist-info}/METADATA +160 -35
  123. webscout-8.3.dist-info/RECORD +290 -0
  124. {webscout-8.2.8.dist-info → webscout-8.3.dist-info}/WHEEL +1 -1
  125. {webscout-8.2.8.dist-info → webscout-8.3.dist-info}/entry_points.txt +1 -0
  126. webscout/Litlogger/Readme.md +0 -175
  127. webscout/Litlogger/core/__init__.py +0 -6
  128. webscout/Litlogger/core/level.py +0 -23
  129. webscout/Litlogger/core/logger.py +0 -165
  130. webscout/Litlogger/handlers/__init__.py +0 -12
  131. webscout/Litlogger/handlers/console.py +0 -33
  132. webscout/Litlogger/handlers/file.py +0 -143
  133. webscout/Litlogger/handlers/network.py +0 -173
  134. webscout/Litlogger/styles/__init__.py +0 -7
  135. webscout/Litlogger/styles/colors.py +0 -249
  136. webscout/Litlogger/styles/formats.py +0 -458
  137. webscout/Litlogger/styles/text.py +0 -87
  138. webscout/Litlogger/utils/__init__.py +0 -6
  139. webscout/Litlogger/utils/detectors.py +0 -153
  140. webscout/Litlogger/utils/formatters.py +0 -200
  141. webscout/Provider/ChatGPTGratis.py +0 -194
  142. webscout/Provider/TTI/AiForce/README.md +0 -159
  143. webscout/Provider/TTI/AiForce/__init__.py +0 -22
  144. webscout/Provider/TTI/AiForce/async_aiforce.py +0 -224
  145. webscout/Provider/TTI/AiForce/sync_aiforce.py +0 -245
  146. webscout/Provider/TTI/FreeAIPlayground/README.md +0 -99
  147. webscout/Provider/TTI/FreeAIPlayground/__init__.py +0 -9
  148. webscout/Provider/TTI/FreeAIPlayground/async_freeaiplayground.py +0 -181
  149. webscout/Provider/TTI/FreeAIPlayground/sync_freeaiplayground.py +0 -180
  150. webscout/Provider/TTI/ImgSys/README.md +0 -174
  151. webscout/Provider/TTI/ImgSys/__init__.py +0 -23
  152. webscout/Provider/TTI/ImgSys/async_imgsys.py +0 -202
  153. webscout/Provider/TTI/ImgSys/sync_imgsys.py +0 -195
  154. webscout/Provider/TTI/MagicStudio/README.md +0 -101
  155. webscout/Provider/TTI/MagicStudio/__init__.py +0 -2
  156. webscout/Provider/TTI/MagicStudio/async_magicstudio.py +0 -111
  157. webscout/Provider/TTI/MagicStudio/sync_magicstudio.py +0 -109
  158. webscout/Provider/TTI/Nexra/README.md +0 -155
  159. webscout/Provider/TTI/Nexra/__init__.py +0 -22
  160. webscout/Provider/TTI/Nexra/async_nexra.py +0 -286
  161. webscout/Provider/TTI/Nexra/sync_nexra.py +0 -258
  162. webscout/Provider/TTI/PollinationsAI/README.md +0 -146
  163. webscout/Provider/TTI/PollinationsAI/__init__.py +0 -23
  164. webscout/Provider/TTI/PollinationsAI/async_pollinations.py +0 -311
  165. webscout/Provider/TTI/PollinationsAI/sync_pollinations.py +0 -265
  166. webscout/Provider/TTI/aiarta/README.md +0 -134
  167. webscout/Provider/TTI/aiarta/__init__.py +0 -2
  168. webscout/Provider/TTI/aiarta/async_aiarta.py +0 -482
  169. webscout/Provider/TTI/aiarta/sync_aiarta.py +0 -440
  170. webscout/Provider/TTI/artbit/README.md +0 -100
  171. webscout/Provider/TTI/artbit/__init__.py +0 -22
  172. webscout/Provider/TTI/artbit/async_artbit.py +0 -155
  173. webscout/Provider/TTI/artbit/sync_artbit.py +0 -148
  174. webscout/Provider/TTI/fastflux/README.md +0 -129
  175. webscout/Provider/TTI/fastflux/__init__.py +0 -22
  176. webscout/Provider/TTI/fastflux/async_fastflux.py +0 -261
  177. webscout/Provider/TTI/fastflux/sync_fastflux.py +0 -252
  178. webscout/Provider/TTI/huggingface/README.md +0 -114
  179. webscout/Provider/TTI/huggingface/__init__.py +0 -22
  180. webscout/Provider/TTI/huggingface/async_huggingface.py +0 -199
  181. webscout/Provider/TTI/huggingface/sync_huggingface.py +0 -195
  182. webscout/Provider/TTI/piclumen/README.md +0 -161
  183. webscout/Provider/TTI/piclumen/__init__.py +0 -23
  184. webscout/Provider/TTI/piclumen/async_piclumen.py +0 -268
  185. webscout/Provider/TTI/piclumen/sync_piclumen.py +0 -233
  186. webscout/Provider/TTI/pixelmuse/README.md +0 -79
  187. webscout/Provider/TTI/pixelmuse/__init__.py +0 -4
  188. webscout/Provider/TTI/pixelmuse/async_pixelmuse.py +0 -249
  189. webscout/Provider/TTI/pixelmuse/sync_pixelmuse.py +0 -182
  190. webscout/Provider/TTI/talkai/README.md +0 -139
  191. webscout/Provider/TTI/talkai/__init__.py +0 -4
  192. webscout/Provider/TTI/talkai/async_talkai.py +0 -229
  193. webscout/Provider/TTI/talkai/sync_talkai.py +0 -207
  194. webscout/Provider/UNFINISHED/oivscode.py +0 -351
  195. webscout-8.2.8.dist-info/RECORD +0 -334
  196. {webscout-8.2.8.dist-info → webscout-8.3.dist-info}/licenses/LICENSE.md +0 -0
  197. {webscout-8.2.8.dist-info → webscout-8.3.dist-info}/top_level.txt +0 -0
webscout/AIauto.py CHANGED
@@ -14,13 +14,16 @@ import inspect
14
14
 
15
15
  def load_providers():
16
16
  """
17
- Dynamically loads all Provider classes from the webscout.Provider package.
18
-
19
- Identifies providers that require special authentication parameters like
20
- 'api_key', 'cookie_file', or 'cookie_path'.
21
-
17
+ Dynamically loads all Provider classes from the `webscout.Provider` package.
18
+
19
+ This function iterates through the modules in the `webscout.Provider` package,
20
+ imports each module, and inspects its attributes to identify classes that
21
+ inherit from the `Provider` base class. It also identifies providers that
22
+ require special authentication parameters like 'api_key', 'cookie_file', or
23
+ 'cookie_path'.
24
+
22
25
  Returns:
23
- tuple: A tuple containing:
26
+ tuple: A tuple containing two elements:
24
27
  - provider_map (dict): A dictionary mapping uppercase provider names to their classes.
25
28
  - api_key_providers (set): A set of uppercase provider names requiring special authentication.
26
29
  """
@@ -42,15 +45,18 @@ def load_providers():
42
45
  api_key_providers.add(attr_name.upper())
43
46
  if 'cookie_file' in sig or 'cookie_path' in sig:
44
47
  cookie_providers.add(attr_name.upper())
45
- except Exception:
46
- pass
48
+ except Exception as e:
49
+ print(f"Error loading provider {module_name}: {e}")
47
50
  return provider_map, api_key_providers.union(cookie_providers)
48
51
 
49
52
  provider_map, api_key_providers = load_providers()
50
53
 
51
54
  class AUTO(Provider):
52
55
  """
53
- An automatic provider that cycles through available free providers
56
+ An automatic provider that intelligently selects and utilizes an available
57
+ LLM provider from the webscout library.
58
+
59
+ It cycles through available free providers
54
60
  until one successfully processes the request. Excludes providers
55
61
  requiring API keys or cookies by default.
56
62
  """
@@ -69,7 +75,10 @@ class AUTO(Provider):
69
75
  print_provider_info: bool = False,
70
76
  ):
71
77
  """
72
- Initializes the AUTO provider.
78
+ Initializes the AUTO provider, setting up the parameters for provider selection and request handling.
79
+
80
+ This constructor initializes the AUTO provider with various configuration options,
81
+ including conversation settings, request limits, and provider exclusions.
73
82
 
74
83
  Args:
75
84
  is_conversation (bool): Flag for conversational mode. Defaults to True.
@@ -102,14 +111,20 @@ class AUTO(Provider):
102
111
  @property
103
112
  def last_response(self) -> dict[str, Any]:
104
113
  """
105
- Returns the last response dictionary from the successful provider.
114
+ Retrieves the last response dictionary from the successfully used provider.
115
+
116
+ Returns:
117
+ dict[str, Any]: The last response dictionary, or an empty dictionary if no provider has been used yet.
106
118
  """
107
119
  return self.provider.last_response if self.provider else {}
108
120
 
109
121
  @property
110
122
  def conversation(self) -> object:
111
123
  """
112
- Returns the conversation object from the successful provider.
124
+ Retrieves the conversation object from the successfully used provider.
125
+
126
+ Returns:
127
+ object: The conversation object, or None if no provider has been used yet.
113
128
  """
114
129
  return self.provider.conversation if self.provider else None
115
130
 
@@ -122,8 +137,11 @@ class AUTO(Provider):
122
137
  conversationally: bool = False,
123
138
  ) -> Union[Dict, Generator]:
124
139
  """
125
- Sends the prompt to providers, trying each available free provider
126
- in a random order until one succeeds.
140
+ Sends the prompt to available providers, attempting to get a response from each until one succeeds.
141
+
142
+ This method iterates through a shuffled list of available providers (excluding those requiring API keys or
143
+ specified in the exclusion list) and attempts to send the prompt to each provider until a successful response
144
+ is received.
127
145
 
128
146
  Args:
129
147
  prompt (str): The user's prompt.
@@ -187,7 +205,7 @@ class AUTO(Provider):
187
205
  conversationally: bool = False,
188
206
  ) -> Union[str, Generator[str, None, None]]:
189
207
  """
190
- Provides a simplified chat interface, returning the message string(s).
208
+ Provides a simplified chat interface, returning the message string or a generator of message strings.
191
209
 
192
210
  Args:
193
211
  prompt (str): The user's prompt.
@@ -218,7 +236,7 @@ class AUTO(Provider):
218
236
  Extracts the message text from the provider's response dictionary.
219
237
 
220
238
  Args:
221
- response (dict): The response dictionary from the ask method.
239
+ response (dict): The response dictionary obtained from the `ask` method.
222
240
 
223
241
  Returns:
224
242
  str: The extracted message string.
webscout/AIbase.py CHANGED
@@ -1,13 +1,37 @@
1
1
  from abc import ABC, abstractmethod
2
2
  from pathlib import Path
3
- from typing import AsyncGenerator, List, Union, Generator, Optional
3
+ from typing import AsyncGenerator, Dict, List, Union, Generator, Optional
4
4
  from typing_extensions import TypeAlias
5
5
 
6
6
  # Type aliases for better readability
7
7
  Response: TypeAlias = dict[str, Union[str, bool, None]]
8
- ImageData: TypeAlias = Union[bytes, str, Generator[bytes, None, None]]
9
8
  AsyncImageData: TypeAlias = Union[bytes, str, AsyncGenerator[bytes, None]]
10
9
 
10
+ class SearchResponse:
11
+ """A wrapper class for search API responses.
12
+
13
+ This class automatically converts response objects to their text representation
14
+ when printed or converted to string.
15
+
16
+ Attributes:
17
+ text (str): The text content of the response
18
+
19
+ Example:
20
+ >>> response = SearchResponse("Hello, world!")
21
+ >>> print(response)
22
+ Hello, world!
23
+ >>> str(response)
24
+ 'Hello, world!'
25
+ """
26
+ def __init__(self, text: str):
27
+ self.text = text
28
+
29
+ def __str__(self):
30
+ return self.text
31
+
32
+ def __repr__(self):
33
+ return self.text
34
+
11
35
  class AIProviderError(Exception):
12
36
  pass
13
37
 
@@ -213,48 +237,83 @@ class AsyncTTSProvider(ABC):
213
237
  async with aiofiles.open(audio_file, 'rb') as f:
214
238
  while chunk := await f.read(chunk_size):
215
239
  yield chunk
216
-
217
- class ImageProvider(ABC):
218
-
219
- @abstractmethod
220
- def generate(self, prompt: str, amount: int = 1) -> List[bytes]:
221
- raise NotImplementedError("Method needs to be implemented in subclass")
222
-
223
- @abstractmethod
224
- def save(
225
- self,
226
- response: List[bytes],
227
- name: Optional[str] = None,
228
- dir: Optional[Union[str, Path]] = None
229
- ) -> List[str]:
230
- raise NotImplementedError("Method needs to be implemented in subclass")
231
-
232
- class AsyncImageProvider(ABC):
233
-
240
+
241
+ class AISearch(ABC):
242
+ """Abstract base class for AI-powered search providers.
243
+
244
+ This class defines the interface for AI search providers that can perform
245
+ web searches and return AI-generated responses based on search results.
246
+
247
+ All search providers should inherit from this class and implement the
248
+ required methods.
249
+ """
250
+
234
251
  @abstractmethod
235
- async def generate(
252
+ def search(
236
253
  self,
237
254
  prompt: str,
238
- amount: int = 1
239
- ) -> Union[AsyncGenerator[bytes, None], List[bytes]]:
240
- raise NotImplementedError("Method needs to be implemented in subclass")
241
-
242
- @abstractmethod
243
- async def save(
244
- self,
245
- response: Union[AsyncGenerator[bytes, None], List[bytes]],
246
- name: Optional[str] = None,
247
- dir: Optional[Union[str, Path]] = None
248
- ) -> List[str]:
255
+ stream: bool = False,
256
+ raw: bool = False,
257
+ ) -> Union[SearchResponse, Generator[Union[Dict[str, str], SearchResponse], None, None]]:
258
+ """Search using the provider's API and get AI-generated responses.
259
+
260
+ This method sends a search query to the provider and returns the AI-generated response.
261
+ It supports both streaming and non-streaming modes, as well as raw response format.
262
+
263
+ Args:
264
+ prompt (str): The search query or prompt to send to the API.
265
+ stream (bool, optional): If True, yields response chunks as they arrive.
266
+ If False, returns complete response. Defaults to False.
267
+ raw (bool, optional): If True, returns raw response dictionaries.
268
+ If False, returns SearchResponse objects that convert to text automatically.
269
+ Defaults to False.
270
+
271
+ Returns:
272
+ Union[SearchResponse, Generator[Union[Dict[str, str], SearchResponse], None, None]]:
273
+ - If stream=False: Returns complete response as SearchResponse object
274
+ - If stream=True: Yields response chunks as either Dict or SearchResponse objects
275
+
276
+ Raises:
277
+ APIConnectionError: If the API request fails
278
+ """
249
279
  raise NotImplementedError("Method needs to be implemented in subclass")
250
280
 
251
- class AISearch(ABC):
252
-
281
+ class AsyncAISearch(ABC):
282
+ """Abstract base class for asynchronous AI-powered search providers.
283
+
284
+ This class defines the interface for asynchronous AI search providers that can perform
285
+ web searches and return AI-generated responses based on search results.
286
+
287
+ All asynchronous search providers should inherit from this class and implement the
288
+ required methods.
289
+ """
290
+
253
291
  @abstractmethod
254
- def search(
292
+ async def search(
255
293
  self,
256
294
  prompt: str,
257
295
  stream: bool = False,
258
296
  raw: bool = False,
259
- ) -> Response:
260
- raise NotImplementedError("Method needs to be implemented in subclass")
297
+ ) -> Union[SearchResponse, AsyncGenerator[Union[Dict[str, str], SearchResponse], None]]:
298
+ """Search using the provider's API and get AI-generated responses asynchronously.
299
+
300
+ This method sends a search query to the provider and returns the AI-generated response.
301
+ It supports both streaming and non-streaming modes, as well as raw response format.
302
+
303
+ Args:
304
+ prompt (str): The search query or prompt to send to the API.
305
+ stream (bool, optional): If True, yields response chunks as they arrive.
306
+ If False, returns complete response. Defaults to False.
307
+ raw (bool, optional): If True, returns raw response dictionaries.
308
+ If False, returns SearchResponse objects that convert to text automatically.
309
+ Defaults to False.
310
+
311
+ Returns:
312
+ Union[SearchResponse, AsyncGenerator[Union[Dict[str, str], SearchResponse], None]]:
313
+ - If stream=False: Returns complete response as SearchResponse object
314
+ - If stream=True: Yields response chunks as either Dict or SearchResponse objects
315
+
316
+ Raises:
317
+ APIConnectionError: If the API request fails
318
+ """
319
+ raise NotImplementedError("Method needs to be implemented in subclass")