Script 'mail_helper' called by obssrc
Hello community,
here is the log from the commit of package python-duckduckgo-search for
openSUSE:Factory checked in at 2025-04-30 19:05:48
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-duckduckgo-search (Old)
and /work/SRC/openSUSE:Factory/.python-duckduckgo-search.new.30101 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-duckduckgo-search"
Wed Apr 30 19:05:48 2025 rev:4 rq:1273675 version:8.0.1
Changes:
--------
---
/work/SRC/openSUSE:Factory/python-duckduckgo-search/python-duckduckgo-search.changes
2025-01-02 19:24:22.899797131 +0100
+++
/work/SRC/openSUSE:Factory/.python-duckduckgo-search.new.30101/python-duckduckgo-search.changes
2025-04-30 19:06:01.930453060 +0200
@@ -1,0 +2,71 @@
+Wed Apr 30 11:05:28 UTC 2025 - Felix Stegmeier <[email protected]>
+
+- Update to 8.0.1:
+ * refactor: remove dead code
+ * refactor: remove dead code
+ * refactor: bump to primp=0.15.0
+
+- Update to 8.0.0:
+ * Chat moved to duckai package.
+ * feat(chat): remove chat
+ * fix(typing): fix typing in cli and tests
+
+- Update to 7.5.5:
+ * fix(chat): add _chat_xfe
+
+- Update to 7.5.4:
+ * fix(chat): x-vqd-hash-1 = ""
+
+- Update to 7.5.3:
+ * DDGS.chat: bugfix by @deedy5 in #294
+
+- Update to 7.5.2:
+ * fix(temp): don't set Client.headers
+ * Full Changelog: v7.5.1...v7.5.2
+
+- Update to 7.5.1:
+ * Bugfix DDGS.text() payload by @deedy5 in #291
+
+- Update to 7.5.0:
+ * chore: bump primp to v0.14.0
+ * tests: sleep 2 seconds between tests
+ * feat(chat): add mistral-small-3
+ * feat(chat): stream response
+ * feat(cli chat): stream response
+
+- Update to 7.4.5:
+ * Chat: bugfix ConversationLimitException by @deedy5 in #288
+
+- Update to 7.4.4:
+ * DDGS.chat: add mistral-small-3 by @deedy5 in #285
+ * fix(patch): patch only while sending request
+
+- Update to 7.4.3:
+ * feat: patch httpcore
+ * feat: improve ssl_context
+
+- Update to 7.4.2:
+ * Use httpx for requests by @deedy5 in #282
+ * Cli(chat): stream answer, add DDGS.chat_yield (response message generator)
by @deedy5 in #283
+
+- Update to 7.3.2:
+ * DDGS.chat: add llama-3.3-70b model by @deedy5 in #281
+
+- Update to 7.3.1:
+ * DDGS.chat: add o3-mini model by @deedy5 in #280
+
+- Update to 7.3.0:
+ * clarify exceptions in README by @vpoulailleau in #275
+ * Drop Support for Python 3.8 by @deedy5 in #276
+ * Bump primp to v0.11.0
+ * Add _impersonates_os
+
+- Update to 7.2.1:
+ * Bump primp to v0.10.0
+ * DDGS._impersonates: add "firefox_128"
+
+- Update to 7.2.1:
+ * Bump primp to v0.10.0
+ * DDGS._impersonates: add "firefox_128"
+
+-------------------------------------------------------------------
Old:
----
duckduckgo_search-7.1.1.tar.gz
New:
----
duckduckgo_search-8.0.1.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-duckduckgo-search.spec ++++++
--- /var/tmp/diff_new_pack.mRj9wI/_old 2025-04-30 19:06:02.502476912 +0200
+++ /var/tmp/diff_new_pack.mRj9wI/_new 2025-04-30 19:06:02.502476912 +0200
@@ -18,7 +18,7 @@
%{?sle15_python_module_pythons}
Name: python-duckduckgo-search
-Version: 7.1.1
+Version: 8.0.1
Release: 0
Summary: Search using the DuckDuckGo.com search engine
License: MIT
@@ -31,17 +31,17 @@
BuildRequires: %{python_module wheel}
BuildRequires: python-rpm-macros
# SECTION test requirements
-BuildRequires: %{python_module click >= 8.1.7}
-BuildRequires: %{python_module primp >= 0.6.3}
+BuildRequires: %{python_module click >= 8.1.8}
+BuildRequires: %{python_module primp >= 0.15.0}
# /SECTION
BuildRequires: fdupes
-Requires: python-click >= 8.1.7
-Requires: python-primp >= 0.6.3
-Suggests: python-lxml >= 5.2.2
-Suggests: python-mypy >= 1.11.1
-Suggests: python-pytest >= 8.3.1
-Suggests: python-pytest-asyncio >= 0.23.8
-Suggests: python-ruff >= 0.6.1
+Requires: python-click >= 8.1.8
+Requires: python-lxml >= 5.3.0
+Requires: python-primp >= 0.15.0
+Suggests: python-mypy >= 1.14.1
+Suggests: python-pytest >= 8.3.4
+Suggests: python-pytest-dependency >= 0.6.0
+Suggests: python-ruff >= 0.9.2
Requires(post): update-alternatives
Requires(postun): update-alternatives
BuildArch: noarch
++++++ duckduckgo_search-7.1.1.tar.gz -> duckduckgo_search-8.0.1.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/duckduckgo_search-7.1.1/PKG-INFO
new/duckduckgo_search-8.0.1/PKG-INFO
--- old/duckduckgo_search-7.1.1/PKG-INFO 2024-12-27 00:22:58.106326300
+0100
+++ new/duckduckgo_search-8.0.1/PKG-INFO 2025-04-17 14:37:28.462133000
+0200
@@ -1,6 +1,6 @@
-Metadata-Version: 2.1
+Metadata-Version: 2.4
Name: duckduckgo_search
-Version: 7.1.1
+Version: 8.0.1
Summary: Search for words, documents, images, news, maps and text translation
using the DuckDuckGo.com search engine.
Author: deedy5
License: MIT License
@@ -11,7 +11,6 @@
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
-Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
@@ -20,22 +19,25 @@
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Topic :: Internet :: WWW/HTTP :: Indexing/Search
Classifier: Topic :: Software Development :: Libraries :: Python Modules
-Requires-Python: >=3.8
+Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE.md
-Requires-Dist: click>=8.1.7
-Requires-Dist: primp>=0.9.2
+Requires-Dist: click>=8.1.8
+Requires-Dist: primp>=0.15.0
Requires-Dist: lxml>=5.3.0
Provides-Extra: dev
-Requires-Dist: mypy>=1.13.0; extra == "dev"
+Requires-Dist: mypy>=1.14.1; extra == "dev"
Requires-Dist: pytest>=8.3.4; extra == "dev"
Requires-Dist: pytest-dependency>=0.6.0; extra == "dev"
-Requires-Dist: ruff>=0.8.3; extra == "dev"
+Requires-Dist: ruff>=0.9.2; extra == "dev"
+Dynamic: license-file
-
[](https://github.com/deedy5/duckduckgo_search/releases)
[](https://pypi.org/project/duckduckgo-search)
[](https://pepy.tech/project/duckduckgo-search)
[](https://pepy.tech/project/duckduckgo-search)
+
[](https://github.com/deedy5/duckduckgo_search/releases)
[](https://pypi.org/project/duckduckgo-search)
# Duckduckgo_search<a name="TOP"></a>
-AI chat and search for text, news, images and videos using the DuckDuckGo.com
search engine.
+Search for text, news, images and videos using the DuckDuckGo.com search
engine.
+
+:bangbang: AI chat moved to [duckai](https://pypi.org/project/duckai) package
## Table of Contents
* [Install](#install)
@@ -45,11 +47,10 @@
* [DDGS class](#ddgs-class)
* [Proxy](#proxy)
* [Exceptions](#exceptions)
-* [1. chat() - AI chat](#1-chat---ai-chat)
-* [2. text() - text search](#2-text---text-search-by-duckduckgocom)
-* [3. images() - image search](#3-images---image-search-by-duckduckgocom)
-* [4. videos() - video search](#4-videos---video-search-by-duckduckgocom)
-* [5. news() - news search](#5-news---news-search-by-duckduckgocom)
+* [1. text() - text search](#2-text---text-search-by-duckduckgocom)
+* [2. images() - image search](#3-images---image-search-by-duckduckgocom)
+* [3. videos() - video search](#4-videos---video-search-by-duckduckgocom)
+* [4. news() - news search](#5-news---news-search-by-duckduckgocom)
* [Disclaimer](#disclaimer)
## Install
@@ -64,8 +65,6 @@
```
CLI examples:
```python3
-# AI chat
-ddgs chat
# text search
ddgs text -k "Assyrian siege of Jerusalem"
# find and download pdf files via proxy
@@ -227,38 +226,24 @@
## Exceptions
+```python
+from duckduckgo_search.exceptions import (
+ ConversationLimitException,
+ DuckDuckGoSearchException,
+ RatelimitException,
+ TimeoutException,
+)
+```
+
Exceptions:
- `DuckDuckGoSearchException`: Base exception for duckduckgo_search errors.
- `RatelimitException`: Inherits from DuckDuckGoSearchException, raised for
exceeding API request rate limits.
- `TimeoutException`: Inherits from DuckDuckGoSearchException, raised for API
request timeouts.
-
-
-[Go To TOP](#TOP)
-
-## 1. chat() - AI chat
-
-```python
-def chat(self, keywords: str, model: str = "gpt-4o-mini", timeout: int = 30)
-> str:
- """Initiates a chat session with DuckDuckGo AI.
-
- Args:
- keywords (str): The initial message or question to send to the AI.
- model (str): The model to use: "gpt-4o-mini", "claude-3-haiku",
"llama-3.1-70b", "mixtral-8x7b".
- Defaults to "gpt-4o-mini".
- timeout (int): Timeout value for the HTTP client. Defaults to 30.
-
- Returns:
- str: The response from the AI.
- """
-```
-***Example***
-```python
-results = DDGS().chat("summarize Daniel Defoe's The Consolidator",
model='claude-3-haiku')
-```
+- `ConversationLimitException`: Inherits from DuckDuckGoSearchException,
raised for conversation limit during API requests to AI endpoint.
[Go To TOP](#TOP)
-## 2. text() - text search by duckduckgo.com
+## 1. text() - text search by duckduckgo.com
```python
def text(
@@ -276,12 +261,10 @@
region: wt-wt, us-en, uk-en, ru-ru, etc. Defaults to "wt-wt".
safesearch: on, moderate, off. Defaults to "moderate".
timelimit: d, w, m, y. Defaults to None.
- backend: auto, api, html, lite. Defaults to auto.
+ backend: auto, html, lite. Defaults to auto.
auto - try all backends in random order,
- api - collect data from https://duckduckgo.com,
html - collect data from https://html.duckduckgo.com,
- lite - collect data from https://lite.duckduckgo.com,
- ecosia - collect data from https://www.ecosia.com.
+ lite - collect data from https://lite.duckduckgo.com.
max_results: max number of results. If None, returns results only from
the first response. Defaults to None.
Returns:
@@ -305,7 +288,7 @@
[Go To TOP](#TOP)
-## 3. images() - image search by duckduckgo.com
+## 2. images() - image search by duckduckgo.com
```python
def images(
@@ -372,7 +355,7 @@
[Go To TOP](#TOP)
-## 4. videos() - video search by duckduckgo.com
+## 3. videos() - video search by duckduckgo.com
```python
def videos(
@@ -439,7 +422,7 @@
[Go To TOP](#TOP)
-## 5. news() - news search by duckduckgo.com
+## 4. news() - news search by duckduckgo.com
```python
def news(
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/duckduckgo_search-7.1.1/README.md
new/duckduckgo_search-8.0.1/README.md
--- old/duckduckgo_search-7.1.1/README.md 2024-12-27 00:22:48.000000000
+0100
+++ new/duckduckgo_search-8.0.1/README.md 2025-04-17 14:37:17.000000000
+0200
@@ -1,7 +1,9 @@
-
[](https://github.com/deedy5/duckduckgo_search/releases)
[](https://pypi.org/project/duckduckgo-search)
[](https://pepy.tech/project/duckduckgo-search)
[](https://pepy.tech/project/duckduckgo-search)
+
[](https://github.com/deedy5/duckduckgo_search/releases)
[](https://pypi.org/project/duckduckgo-search)
# Duckduckgo_search<a name="TOP"></a>
-AI chat and search for text, news, images and videos using the DuckDuckGo.com
search engine.
+Search for text, news, images and videos using the DuckDuckGo.com search
engine.
+
+:bangbang: AI chat moved to [duckai](https://pypi.org/project/duckai) package
## Table of Contents
* [Install](#install)
@@ -11,11 +13,10 @@
* [DDGS class](#ddgs-class)
* [Proxy](#proxy)
* [Exceptions](#exceptions)
-* [1. chat() - AI chat](#1-chat---ai-chat)
-* [2. text() - text search](#2-text---text-search-by-duckduckgocom)
-* [3. images() - image search](#3-images---image-search-by-duckduckgocom)
-* [4. videos() - video search](#4-videos---video-search-by-duckduckgocom)
-* [5. news() - news search](#5-news---news-search-by-duckduckgocom)
+* [1. text() - text search](#2-text---text-search-by-duckduckgocom)
+* [2. images() - image search](#3-images---image-search-by-duckduckgocom)
+* [3. videos() - video search](#4-videos---video-search-by-duckduckgocom)
+* [4. news() - news search](#5-news---news-search-by-duckduckgocom)
* [Disclaimer](#disclaimer)
## Install
@@ -30,8 +31,6 @@
```
CLI examples:
```python3
-# AI chat
-ddgs chat
# text search
ddgs text -k "Assyrian siege of Jerusalem"
# find and download pdf files via proxy
@@ -193,38 +192,24 @@
## Exceptions
+```python
+from duckduckgo_search.exceptions import (
+ ConversationLimitException,
+ DuckDuckGoSearchException,
+ RatelimitException,
+ TimeoutException,
+)
+```
+
Exceptions:
- `DuckDuckGoSearchException`: Base exception for duckduckgo_search errors.
- `RatelimitException`: Inherits from DuckDuckGoSearchException, raised for
exceeding API request rate limits.
- `TimeoutException`: Inherits from DuckDuckGoSearchException, raised for API
request timeouts.
-
-
-[Go To TOP](#TOP)
-
-## 1. chat() - AI chat
-
-```python
-def chat(self, keywords: str, model: str = "gpt-4o-mini", timeout: int = 30)
-> str:
- """Initiates a chat session with DuckDuckGo AI.
-
- Args:
- keywords (str): The initial message or question to send to the AI.
- model (str): The model to use: "gpt-4o-mini", "claude-3-haiku",
"llama-3.1-70b", "mixtral-8x7b".
- Defaults to "gpt-4o-mini".
- timeout (int): Timeout value for the HTTP client. Defaults to 30.
-
- Returns:
- str: The response from the AI.
- """
-```
-***Example***
-```python
-results = DDGS().chat("summarize Daniel Defoe's The Consolidator",
model='claude-3-haiku')
-```
+- `ConversationLimitException`: Inherits from DuckDuckGoSearchException,
raised for conversation limit during API requests to AI endpoint.
[Go To TOP](#TOP)
-## 2. text() - text search by duckduckgo.com
+## 1. text() - text search by duckduckgo.com
```python
def text(
@@ -242,12 +227,10 @@
region: wt-wt, us-en, uk-en, ru-ru, etc. Defaults to "wt-wt".
safesearch: on, moderate, off. Defaults to "moderate".
timelimit: d, w, m, y. Defaults to None.
- backend: auto, api, html, lite. Defaults to auto.
+ backend: auto, html, lite. Defaults to auto.
auto - try all backends in random order,
- api - collect data from https://duckduckgo.com,
html - collect data from https://html.duckduckgo.com,
- lite - collect data from https://lite.duckduckgo.com,
- ecosia - collect data from https://www.ecosia.com.
+ lite - collect data from https://lite.duckduckgo.com.
max_results: max number of results. If None, returns results only from
the first response. Defaults to None.
Returns:
@@ -271,7 +254,7 @@
[Go To TOP](#TOP)
-## 3. images() - image search by duckduckgo.com
+## 2. images() - image search by duckduckgo.com
```python
def images(
@@ -338,7 +321,7 @@
[Go To TOP](#TOP)
-## 4. videos() - video search by duckduckgo.com
+## 3. videos() - video search by duckduckgo.com
```python
def videos(
@@ -405,7 +388,7 @@
[Go To TOP](#TOP)
-## 5. news() - news search by duckduckgo.com
+## 4. news() - news search by duckduckgo.com
```python
def news(
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/duckduckgo_search-7.1.1/duckduckgo_search/cli.py
new/duckduckgo_search-8.0.1/duckduckgo_search/cli.py
--- old/duckduckgo_search-7.1.1/duckduckgo_search/cli.py 2024-12-27
00:22:48.000000000 +0100
+++ new/duckduckgo_search-8.0.1/duckduckgo_search/cli.py 2025-04-17
14:37:17.000000000 +0200
@@ -1,7 +1,8 @@
+from __future__ import annotations
+
import csv
import logging
import os
-import sys
from concurrent.futures import ThreadPoolExecutor, as_completed
from datetime import datetime
from pathlib import Path
@@ -11,7 +12,7 @@
import primp
from .duckduckgo_search import DDGS
-from .utils import _expand_proxy_tb_alias, json_dumps, json_loads
+from .utils import _expand_proxy_tb_alias, json_dumps
from .version import __version__
logger = logging.getLogger(__name__)
@@ -36,7 +37,7 @@
}
-def _save_data(keywords, data, function_name, filename):
+def _save_data(keywords: str, data: list[dict[str, str]], function_name: str,
filename: str | None) -> None:
filename, ext = filename.rsplit(".", 1) if filename and
filename.endswith((".csv", ".json")) else (None, filename)
filename = filename if filename else
f"{function_name}_{keywords}_{datetime.now():%Y%m%d_%H%M%S}"
if ext == "csv":
@@ -45,12 +46,12 @@
_save_json(f"{filename}.{ext}", data)
-def _save_json(jsonfile, data):
+def _save_json(jsonfile: str | Path, data: list[dict[str, str]]) -> None:
with open(jsonfile, "w", encoding="utf-8") as file:
file.write(json_dumps(data))
-def _save_csv(csvfile, data):
+def _save_csv(csvfile: str | Path, data: list[dict[str, str]]) -> None:
with open(csvfile, "w", newline="", encoding="utf-8") as file:
if data:
headers = data[0].keys()
@@ -59,7 +60,7 @@
writer.writerows(data)
-def _print_data(data):
+def _print_data(data: list[dict[str, str]]) -> None:
if data:
for i, e in enumerate(data, start=1):
click.secho(f"{i}.\t {'=' * 78}", bg="black", fg="white")
@@ -76,7 +77,7 @@
input()
-def _sanitize_keywords(keywords):
+def _sanitize_keywords(keywords: str) -> str:
keywords = (
keywords.replace("filetype", "")
.replace(":", "")
@@ -90,9 +91,11 @@
return keywords
-def _download_file(url, dir_path, filename, proxy, verify):
+def _download_file(url: str, dir_path: str, filename: str, proxy: str | None,
verify: bool) -> None:
try:
- resp = primp.Client(proxy=proxy, impersonate="chrome_131", timeout=10,
verify=verify).get(url)
+ resp = primp.Client(proxy=proxy, impersonate="random",
impersonate_os="random", timeout=10, verify=verify).get(
+ url
+ )
if resp.status_code == 200:
with open(os.path.join(dir_path, filename[:200]), "wb") as file:
file.write(resp.content)
@@ -100,7 +103,15 @@
logger.debug(f"download_file url={url} {type(ex).__name__} {ex}")
-def _download_results(keywords, results, function_name, proxy=None,
threads=None, verify=True, pathname=None):
+def _download_results(
+ keywords: str,
+ results: list[dict[str, str]],
+ function_name: str,
+ proxy: str | None = None,
+ threads: int | None = None,
+ verify: bool = True,
+ pathname: str | None = None,
+) -> None:
path = pathname if pathname else
f"{function_name}_{keywords}_{datetime.now():%Y%m%d_%H%M%S}"
os.makedirs(path, exist_ok=True)
@@ -113,7 +124,7 @@
f = executor.submit(_download_file, url, path, f"{i}_{filename}",
proxy, verify)
futures.append(f)
- with click.progressbar(
+ with click.progressbar( # type: ignore
length=len(futures), label="Downloading", show_percent=True,
show_pos=True, width=50
) as bar:
for future in as_completed(futures):
@@ -122,12 +133,12 @@
@click.group(chain=True)
-def cli():
+def cli() -> None:
"""duckduckgo_search CLI tool"""
pass
-def safe_entry_point():
+def safe_entry_point() -> None:
try:
cli()
except Exception as ex:
@@ -135,60 +146,12 @@
@cli.command()
-def version():
+def version() -> str:
print(__version__)
return __version__
@cli.command()
[email protected]("-l", "--load", is_flag=True, default=False, help="load the last
conversation from the json cache")
[email protected]("-p", "--proxy", help="the proxy to send requests, example:
socks5://127.0.0.1:9150")
[email protected]("-ml", "--multiline", is_flag=True, default=False,
help="multi-line input")
[email protected]("-t", "--timeout", default=30, help="timeout value for the HTTP
client")
[email protected]("-v", "--verify", default=True, help="verify SSL when making the
request")
[email protected](
- "-m",
- "--model",
- prompt="""DuckDuckGo AI chat. Choose a model:
-[1]: gpt-4o-mini
-[2]: claude-3-haiku
-[3]: llama-3.1-70b
-[4]: mixtral-8x7b
-""",
- type=click.Choice(["1", "2", "3", "4"]),
- show_choices=False,
- default="1",
-)
-def chat(load, proxy, multiline, timeout, verify, model):
- """CLI function to perform an interactive AI chat using DuckDuckGo API."""
- client = DDGS(proxy=_expand_proxy_tb_alias(proxy), verify=verify)
- model = ["gpt-4o-mini", "claude-3-haiku", "llama-3.1-70b",
"mixtral-8x7b"][int(model) - 1]
-
- cache_file = "ddgs_chat_conversation.json"
- if load and Path(cache_file).exists():
- with open(cache_file) as f:
- cache = json_loads(f.read())
- client._chat_vqd = cache.get("vqd", None)
- client._chat_messages = cache.get("messages", [])
- client._chat_tokens_count = cache.get("tokens", 0)
-
- while True:
- print(f"{'-'*78}\nYou[{model=} tokens={client._chat_tokens_count}]: ",
end="")
- if multiline:
- print(f"""[multiline, send message: ctrl+{"Z" if sys.platform ==
"win32" else "D"}]""")
- user_input = sys.stdin.read()
- print("...")
- else:
- user_input = input()
- if user_input.strip():
- resp_answer = client.chat(keywords=user_input, model=model,
timeout=timeout)
- click.secho(f"AI: {resp_answer}", fg="green")
-
- cache = {"vqd": client._chat_vqd, "tokens":
client._chat_tokens_count, "messages": client._chat_messages}
- _save_json(cache_file, cache)
-
-
[email protected]()
@click.option("-k", "--keywords", required=True, help="text search, keywords
for query")
@click.option("-r", "--region", default="wt-wt", help="wt-wt, us-en, ru-ru,
etc. -region https://duckduckgo.com/params")
@click.option("-s", "--safesearch", default="moderate",
type=click.Choice(["on", "moderate", "off"]))
@@ -197,24 +160,24 @@
@click.option("-o", "--output", help="csv, json or filename.csv|json (save the
results to a csv or json file)")
@click.option("-d", "--download", is_flag=True, default=False, help="download
results. -dd to set custom directory")
@click.option("-dd", "--download-directory", help="Specify custom download
directory")
[email protected]("-b", "--backend", default="auto", type=click.Choice(["auto",
"api", "html", "lite", "ecosia"]))
[email protected]("-b", "--backend", default="auto", type=click.Choice(["auto",
"html", "lite"]))
@click.option("-th", "--threads", default=10, help="download threads,
default=10")
@click.option("-p", "--proxy", help="the proxy to send requests, example:
socks5://127.0.0.1:9150")
@click.option("-v", "--verify", default=True, help="verify SSL when making the
request")
def text(
- keywords,
- region,
- safesearch,
- timelimit,
- backend,
- output,
- download,
- download_directory,
- threads,
- max_results,
- proxy,
- verify,
-):
+ keywords: str,
+ region: str,
+ safesearch: str,
+ timelimit: str | None,
+ backend: str,
+ output: str | None,
+ download: bool,
+ download_directory: str | None,
+ threads: int,
+ max_results: int | None,
+ proxy: str | None,
+ verify: bool,
+) -> None:
"""CLI function to perform a text search using DuckDuckGo API."""
data = DDGS(proxy=_expand_proxy_tb_alias(proxy), verify=verify).text(
keywords=keywords,
@@ -284,23 +247,23 @@
@click.option("-p", "--proxy", help="the proxy to send requests, example:
socks5://127.0.0.1:9150")
@click.option("-v", "--verify", default=True, help="verify SSL when making the
request")
def images(
- keywords,
- region,
- safesearch,
- timelimit,
- size,
- color,
- type_image,
- layout,
- license_image,
- download,
- download_directory,
- threads,
- max_results,
- output,
- proxy,
- verify,
-):
+ keywords: str,
+ region: str,
+ safesearch: str,
+ timelimit: str | None,
+ size: str | None,
+ color: str | None,
+ type_image: str | None,
+ layout: str | None,
+ license_image: str | None,
+ download: bool,
+ download_directory: str | None,
+ threads: int,
+ max_results: int | None,
+ output: str | None,
+ proxy: str | None,
+ verify: bool,
+) -> None:
"""CLI function to perform a images search using DuckDuckGo API."""
data = DDGS(proxy=_expand_proxy_tb_alias(proxy), verify=verify).images(
keywords=keywords,
@@ -344,8 +307,18 @@
@click.option("-p", "--proxy", help="the proxy to send requests, example:
socks5://127.0.0.1:9150")
@click.option("-v", "--verify", default=True, help="verify SSL when making the
request")
def videos(
- keywords, region, safesearch, timelimit, resolution, duration,
license_videos, max_results, output, proxy, verify
-):
+ keywords: str,
+ region: str,
+ safesearch: str,
+ timelimit: str | None,
+ resolution: str | None,
+ duration: str | None,
+ license_videos: str | None,
+ max_results: int | None,
+ output: str | None,
+ proxy: str | None,
+ verify: bool,
+) -> None:
"""CLI function to perform a videos search using DuckDuckGo API."""
data = DDGS(proxy=_expand_proxy_tb_alias(proxy), verify=verify).videos(
keywords=keywords,
@@ -373,7 +346,16 @@
@click.option("-o", "--output", help="csv, json or filename.csv|json (save the
results to a csv or json file)")
@click.option("-p", "--proxy", help="the proxy to send requests, example:
socks5://127.0.0.1:9150")
@click.option("-v", "--verify", default=True, help="verify SSL when making the
request")
-def news(keywords, region, safesearch, timelimit, max_results, output, proxy,
verify):
+def news(
+ keywords: str,
+ region: str,
+ safesearch: str,
+ timelimit: str | None,
+ max_results: int | None,
+ output: str | None,
+ proxy: str | None,
+ verify: bool,
+) -> None:
"""CLI function to perform a news search using DuckDuckGo API."""
data = DDGS(proxy=_expand_proxy_tb_alias(proxy), verify=verify).news(
keywords=keywords, region=region, safesearch=safesearch,
timelimit=timelimit, max_results=max_results
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/duckduckgo_search-7.1.1/duckduckgo_search/duckduckgo_search.py
new/duckduckgo_search-8.0.1/duckduckgo_search/duckduckgo_search.py
--- old/duckduckgo_search-7.1.1/duckduckgo_search/duckduckgo_search.py
2024-12-27 00:22:48.000000000 +0100
+++ new/duckduckgo_search-8.0.1/duckduckgo_search/duckduckgo_search.py
2025-04-17 14:37:17.000000000 +0200
@@ -6,17 +6,17 @@
from datetime import datetime, timezone
from functools import cached_property
from itertools import cycle
-from random import choice, shuffle
+from random import shuffle
from time import sleep, time
from types import TracebackType
-from typing import cast
+from typing import Any, Literal
-import primp # type: ignore
+import primp
from lxml.etree import _Element
from lxml.html import HTMLParser as LHTMLParser
from lxml.html import document_fromstring
-from .exceptions import ConversationLimitException, DuckDuckGoSearchException,
RatelimitException, TimeoutException
+from .exceptions import DuckDuckGoSearchException, RatelimitException,
TimeoutException
from .utils import (
_expand_proxy_tb_alias,
_extract_vqd,
@@ -31,20 +31,6 @@
class DDGS:
"""DuckDuckgo_search class to get search results from duckduckgo.com."""
- _impersonates = (
- "chrome_100", "chrome_101", "chrome_104", "chrome_105", "chrome_106",
"chrome_107",
- "chrome_108", "chrome_109", "chrome_114", "chrome_116", "chrome_117",
"chrome_118",
- "chrome_119", "chrome_120", "chrome_123", "chrome_124", "chrome_126",
"chrome_127",
- "chrome_128", "chrome_129", "chrome_130", "chrome_131",
- "safari_ios_16.5", "safari_ios_17.2", "safari_ios_17.4.1",
"safari_ios_18.1.1",
- "safari_15.3", "safari_15.5", "safari_15.6.1", "safari_16",
"safari_16.5",
- "safari_17.0", "safari_17.2.1", "safari_17.4.1", "safari_17.5",
- "safari_18", "safari_18.2",
- "safari_ipad_18",
- "edge_101", "edge_122", "edge_127", "edge_131",
- "firefox_109", "firefox_133",
- ) # fmt: skip
-
def __init__(
self,
headers: dict[str, str] | None = None,
@@ -70,19 +56,18 @@
self.proxy = proxies.get("http") or proxies.get("https") if
isinstance(proxies, dict) else proxies
self.headers = headers if headers else {}
self.headers["Referer"] = "https://duckduckgo.com/"
+ self.timeout = timeout
self.client = primp.Client(
- headers=self.headers,
+ # headers=self.headers,
proxy=self.proxy,
- timeout=timeout,
+ timeout=self.timeout,
cookie_store=True,
referer=True,
- impersonate=choice(self._impersonates),
+ impersonate="random",
+ impersonate_os="random",
follow_redirects=False,
verify=verify,
)
- self._chat_messages: list[dict[str, str]] = []
- self._chat_tokens_count = 0
- self._chat_vqd: str = ""
self.sleep_timestamp = 0.0
def __enter__(self) -> DDGS:
@@ -109,99 +94,45 @@
def _get_url(
self,
- method: str,
+ method: Literal["GET", "HEAD", "OPTIONS", "DELETE", "POST", "PUT",
"PATCH"],
url: str,
params: dict[str, str] | None = None,
content: bytes | None = None,
- data: dict[str, str] | bytes | None = None,
+ data: dict[str, str] | None = None,
+ headers: dict[str, str] | None = None,
cookies: dict[str, str] | None = None,
- ) -> bytes:
+ json: Any = None,
+ timeout: float | None = None,
+ ) -> Any:
self._sleep()
try:
- resp = self.client.request(method, url, params=params,
content=content, data=data, cookies=cookies)
+ resp = self.client.request(
+ method,
+ url,
+ params=params,
+ content=content,
+ data=data,
+ headers=headers,
+ cookies=cookies,
+ json=json,
+ timeout=timeout or self.timeout,
+ )
except Exception as ex:
if "time" in str(ex).lower():
raise TimeoutException(f"{url} {type(ex).__name__}: {ex}")
from ex
raise DuckDuckGoSearchException(f"{url} {type(ex).__name__}:
{ex}") from ex
- logger.debug(f"_get_url() {resp.url} {resp.status_code}
{len(resp.content)}")
+ logger.debug(f"_get_url() {resp.url} {resp.status_code}")
if resp.status_code == 200:
- return cast(bytes, resp.content)
- elif resp.status_code in (202, 301, 403):
+ return resp
+ elif resp.status_code in (202, 301, 403, 400, 429, 418):
raise RatelimitException(f"{resp.url} {resp.status_code}
Ratelimit")
raise DuckDuckGoSearchException(f"{resp.url} return None. {params=}
{content=} {data=}")
def _get_vqd(self, keywords: str) -> str:
"""Get vqd value for a search query."""
- resp_content = self._get_url("GET", "https://duckduckgo.com",
params={"q": keywords})
+ resp_content = self._get_url("GET", "https://duckduckgo.com",
params={"q": keywords}).content
return _extract_vqd(resp_content, keywords)
- def chat(self, keywords: str, model: str = "gpt-4o-mini", timeout: int =
30) -> str:
- """Initiates a chat session with DuckDuckGo AI.
-
- Args:
- keywords (str): The initial message or question to send to the AI.
- model (str): The model to use: "gpt-4o-mini", "claude-3-haiku",
"llama-3.1-70b", "mixtral-8x7b".
- Defaults to "gpt-4o-mini".
- timeout (int): Timeout value for the HTTP client. Defaults to 20.
-
- Returns:
- str: The response from the AI.
- """
- models_deprecated = {
- "gpt-3.5": "gpt-4o-mini",
- "llama-3-70b": "llama-3.1-70b",
- }
- if model in models_deprecated:
- logger.info(f"{model=} is deprecated, using
{models_deprecated[model]}")
- model = models_deprecated[model]
- models = {
- "claude-3-haiku": "claude-3-haiku-20240307",
- "gpt-4o-mini": "gpt-4o-mini",
- "llama-3.1-70b": "meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo",
- "mixtral-8x7b": "mistralai/Mixtral-8x7B-Instruct-v0.1",
- }
- # vqd
- if not self._chat_vqd:
- resp =
self.client.get("https://duckduckgo.com/duckchat/v1/status",
headers={"x-vqd-accept": "1"})
- self._chat_vqd = resp.headers.get("x-vqd-4", "")
-
- self._chat_messages.append({"role": "user", "content": keywords})
- self._chat_tokens_count += len(keywords) // 4 if len(keywords) >= 4
else 1 # approximate number of tokens
-
- json_data = {
- "model": models[model],
- "messages": self._chat_messages,
- }
- resp = self.client.post(
- "https://duckduckgo.com/duckchat/v1/chat",
- headers={"x-vqd-4": self._chat_vqd},
- json=json_data,
- timeout=timeout,
- )
- self._chat_vqd = resp.headers.get("x-vqd-4", "")
-
- data = ",".join(x for line in
resp.text.rstrip("[DONE]LIMT_CVRSA\n").split("data:") if (x := line.strip()))
- data = json_loads("[" + data + "]")
-
- results = []
- for x in data:
- if x.get("action") == "error":
- err_message = x.get("type", "")
- if x.get("status") == 429:
- raise (
- ConversationLimitException(err_message)
- if err_message == "ERR_CONVERSATION_LIMIT"
- else RatelimitException(err_message)
- )
- raise DuckDuckGoSearchException(err_message)
- elif message := x.get("message"):
- results.append(message)
- result = "".join(results)
-
- self._chat_messages.append({"role": "assistant", "content": result})
- self._chat_tokens_count += len(results)
- return result
-
def text(
self,
keywords: str,
@@ -218,11 +149,10 @@
region: wt-wt, us-en, uk-en, ru-ru, etc. Defaults to "wt-wt".
safesearch: on, moderate, off. Defaults to "moderate".
timelimit: d, w, m, y. Defaults to None.
- backend: auto, api, html, lite. Defaults to auto.
+ backend: auto, html, lite. Defaults to auto.
auto - try all backends in random order,
html - collect data from https://html.duckduckgo.com,
- lite - collect data from https://lite.duckduckgo.com,
- ecosia - collect data from https://www.ecosia.com.
+ lite - collect data from https://lite.duckduckgo.com.
max_results: max number of results. If None, returns results only
from the first response. Defaults to None.
Returns:
@@ -233,10 +163,10 @@
RatelimitException: Inherits from DuckDuckGoSearchException,
raised for exceeding API request rate limits.
TimeoutException: Inherits from DuckDuckGoSearchException, raised
for API request timeouts.
"""
- if backend == "api":
- warnings.warn("'api' backend is deprecated, using backend='auto'",
stacklevel=2)
+ if backend in ("api", "ecosia"):
+ warnings.warn(f"{backend=} is deprecated, using backend='auto'",
stacklevel=2)
backend = "auto"
- backends = ["html", "lite", "ecosia"] if backend == "auto" else
[backend]
+ backends = ["html", "lite"] if backend == "auto" else [backend]
shuffle(backends)
results, err = [], None
@@ -246,8 +176,6 @@
results = self._text_html(keywords, region, timelimit,
max_results)
elif b == "lite":
results = self._text_lite(keywords, region, timelimit,
max_results)
- elif b == "ecosia":
- results = self._text_ecosia(keywords, region, safesearch,
max_results)
return results
except Exception as ex:
logger.info(f"Error to search using {b} backend: {ex}")
@@ -266,12 +194,8 @@
payload = {
"q": keywords,
- "s": "0",
- "o": "json",
- "api": "d.js",
- "vqd": "",
+ "b": "",
"kl": region,
- "bing_market": region,
}
if timelimit:
payload["df"] = timelimit
@@ -280,7 +204,7 @@
results: list[dict[str, str]] = []
for _ in range(5):
- resp_content = self._get_url("POST",
"https://html.duckduckgo.com/html", data=payload)
+ resp_content = self._get_url("POST",
"https://html.duckduckgo.com/html", data=payload).content
if b"No results." in resp_content:
return results
@@ -338,12 +262,7 @@
payload = {
"q": keywords,
- "s": "0",
- "o": "json",
- "api": "d.js",
- "vqd": "",
"kl": region,
- "bing_market": region,
}
if timelimit:
payload["df"] = timelimit
@@ -352,7 +271,7 @@
results: list[dict[str, str]] = []
for _ in range(5):
- resp_content = self._get_url("POST",
"https://lite.duckduckgo.com/lite/", data=payload)
+ resp_content = self._get_url("POST",
"https://lite.duckduckgo.com/lite/", data=payload).content
if b"No more results." in resp_content:
return results
@@ -397,85 +316,15 @@
if max_results and len(results) >= max_results:
return results
- next_page_s = tree.xpath("//form[./input[contains(@value,
'ext')]]/input[@name='s']/@value")
- if not next_page_s or not max_results:
- return results
- elif isinstance(next_page_s, list):
- payload["s"] = str(next_page_s[0])
-
- return results
-
- def _text_ecosia(
- self,
- keywords: str,
- region: str = "wt-wt",
- safesearch: str = "moderate",
- max_results: int | None = None,
- ) -> list[dict[str, str]]:
- assert keywords, "keywords is mandatory"
-
- payload = {
- "q": keywords,
- }
- cookies = {
- "a": "0",
- "as": "0",
- "cs": "1",
- "dt": "pc",
- "f": "y" if safesearch == "on" else "n" if safesearch == "off"
else "i",
- "fr": "0",
- "fs": "1",
- "l": "en",
- "lt": f"{int(time() * 1000)}",
- "mc": f"{region[3:]}-{region[:2]}",
- "nf": "0",
- "nt": "0",
- "pz": "0",
- "t": "6",
- "tt": "",
- "tu": "auto",
- "wu": "auto",
- "ma": "1",
- }
-
- cache = set()
- results: list[dict[str, str]] = []
-
- for _ in range(5):
- resp_content = self._get_url("GET",
"https://www.ecosia.org/search", params=payload, cookies=cookies)
- if b"Unfortunately we didn\xe2\x80\x99t find any results for" in
resp_content:
- return results
-
- tree = document_fromstring(resp_content, self.parser)
- elements = tree.xpath("//div[@class='result__body']")
- if not isinstance(elements, list):
- return results
-
- for e in elements:
- if isinstance(e, _Element):
- hrefxpath =
e.xpath(".//div[@class='result__title']/a/@href")
- href = str(hrefxpath[0]) if hrefxpath and
isinstance(hrefxpath, list) else None
- if href and href not in cache:
- cache.add(href)
- titlexpath =
e.xpath(".//div[@class='result__title']/a/h2/text()")
- title = str(titlexpath[0]) if titlexpath and
isinstance(titlexpath, list) else ""
- bodyxpath =
e.xpath(".//div[@class='result__description']//text()")
- body = "".join(str(x) for x in bodyxpath) if bodyxpath
and isinstance(bodyxpath, list) else ""
- results.append(
- {
- "title": _normalize(title.strip()),
- "href": _normalize_url(href),
- "body": _normalize(body.strip()),
- }
- )
- if max_results and len(results) >= max_results:
- return results
-
- npx = tree.xpath("//div[contains(@class,
'pagination')]//a[contains(@data-test-id, 'next')]/@href")
+ npx = tree.xpath("//form[./input[contains(@value, 'ext')]]")
if not npx or not max_results:
return results
- if isinstance(npx, list):
- payload["p"] = str(npx[-1]).split("p=")[1].split("&")[0]
+ next_page = npx[-1] if isinstance(npx, list) else None
+ if isinstance(next_page, _Element):
+ names = next_page.xpath('.//input[@type="hidden"]/@name')
+ values = next_page.xpath('.//input[@type="hidden"]/@value')
+ if isinstance(names, list) and isinstance(values, list):
+ payload = {str(n): str(v) for n, v in zip(names, values)}
return results
@@ -543,7 +392,9 @@
results: list[dict[str, str]] = []
for _ in range(5):
- resp_content = self._get_url("GET", "https://duckduckgo.com/i.js",
params=payload)
+ resp_content = self._get_url(
+ "GET", "https://duckduckgo.com/i.js", params=payload,
headers={"Referer": "https://duckduckgo.com/"}
+ ).content
resp_json = json_loads(resp_content)
page_data = resp_json.get("results", [])
@@ -623,7 +474,7 @@
results: list[dict[str, str]] = []
for _ in range(8):
- resp_content = self._get_url("GET", "https://duckduckgo.com/v.js",
params=payload)
+ resp_content = self._get_url("GET", "https://duckduckgo.com/v.js",
params=payload).content
resp_json = json_loads(resp_content)
page_data = resp_json.get("results", [])
@@ -685,7 +536,7 @@
results: list[dict[str, str]] = []
for _ in range(5):
- resp_content = self._get_url("GET",
"https://duckduckgo.com/news.js", params=payload)
+ resp_content = self._get_url("GET",
"https://duckduckgo.com/news.js", params=payload).content
resp_json = json_loads(resp_content)
page_data = resp_json.get("results", [])
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/duckduckgo_search-7.1.1/duckduckgo_search/version.py
new/duckduckgo_search-8.0.1/duckduckgo_search/version.py
--- old/duckduckgo_search-7.1.1/duckduckgo_search/version.py 2024-12-27
00:22:48.000000000 +0100
+++ new/duckduckgo_search-8.0.1/duckduckgo_search/version.py 2025-04-17
14:37:17.000000000 +0200
@@ -1 +1 @@
-__version__ = "7.1.1"
+__version__ = "8.0.1"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/duckduckgo_search-7.1.1/duckduckgo_search.egg-info/PKG-INFO
new/duckduckgo_search-8.0.1/duckduckgo_search.egg-info/PKG-INFO
--- old/duckduckgo_search-7.1.1/duckduckgo_search.egg-info/PKG-INFO
2024-12-27 00:22:58.000000000 +0100
+++ new/duckduckgo_search-8.0.1/duckduckgo_search.egg-info/PKG-INFO
2025-04-17 14:37:28.000000000 +0200
@@ -1,6 +1,6 @@
-Metadata-Version: 2.1
+Metadata-Version: 2.4
Name: duckduckgo_search
-Version: 7.1.1
+Version: 8.0.1
Summary: Search for words, documents, images, news, maps and text translation
using the DuckDuckGo.com search engine.
Author: deedy5
License: MIT License
@@ -11,7 +11,6 @@
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
-Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
@@ -20,22 +19,25 @@
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Topic :: Internet :: WWW/HTTP :: Indexing/Search
Classifier: Topic :: Software Development :: Libraries :: Python Modules
-Requires-Python: >=3.8
+Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE.md
-Requires-Dist: click>=8.1.7
-Requires-Dist: primp>=0.9.2
+Requires-Dist: click>=8.1.8
+Requires-Dist: primp>=0.15.0
Requires-Dist: lxml>=5.3.0
Provides-Extra: dev
-Requires-Dist: mypy>=1.13.0; extra == "dev"
+Requires-Dist: mypy>=1.14.1; extra == "dev"
Requires-Dist: pytest>=8.3.4; extra == "dev"
Requires-Dist: pytest-dependency>=0.6.0; extra == "dev"
-Requires-Dist: ruff>=0.8.3; extra == "dev"
+Requires-Dist: ruff>=0.9.2; extra == "dev"
+Dynamic: license-file
-
[](https://github.com/deedy5/duckduckgo_search/releases)
[](https://pypi.org/project/duckduckgo-search)
[](https://pepy.tech/project/duckduckgo-search)
[](https://pepy.tech/project/duckduckgo-search)
+
[](https://github.com/deedy5/duckduckgo_search/releases)
[](https://pypi.org/project/duckduckgo-search)
# Duckduckgo_search<a name="TOP"></a>
-AI chat and search for text, news, images and videos using the DuckDuckGo.com
search engine.
+Search for text, news, images and videos using the DuckDuckGo.com search
engine.
+
+:bangbang: AI chat moved to [duckai](https://pypi.org/project/duckai) package
## Table of Contents
* [Install](#install)
@@ -45,11 +47,10 @@
* [DDGS class](#ddgs-class)
* [Proxy](#proxy)
* [Exceptions](#exceptions)
-* [1. chat() - AI chat](#1-chat---ai-chat)
-* [2. text() - text search](#2-text---text-search-by-duckduckgocom)
-* [3. images() - image search](#3-images---image-search-by-duckduckgocom)
-* [4. videos() - video search](#4-videos---video-search-by-duckduckgocom)
-* [5. news() - news search](#5-news---news-search-by-duckduckgocom)
+* [1. text() - text search](#2-text---text-search-by-duckduckgocom)
+* [2. images() - image search](#3-images---image-search-by-duckduckgocom)
+* [3. videos() - video search](#4-videos---video-search-by-duckduckgocom)
+* [4. news() - news search](#5-news---news-search-by-duckduckgocom)
* [Disclaimer](#disclaimer)
## Install
@@ -64,8 +65,6 @@
```
CLI examples:
```python3
-# AI chat
-ddgs chat
# text search
ddgs text -k "Assyrian siege of Jerusalem"
# find and download pdf files via proxy
@@ -227,38 +226,24 @@
## Exceptions
+```python
+from duckduckgo_search.exceptions import (
+ ConversationLimitException,
+ DuckDuckGoSearchException,
+ RatelimitException,
+ TimeoutException,
+)
+```
+
Exceptions:
- `DuckDuckGoSearchException`: Base exception for duckduckgo_search errors.
- `RatelimitException`: Inherits from DuckDuckGoSearchException, raised for
exceeding API request rate limits.
- `TimeoutException`: Inherits from DuckDuckGoSearchException, raised for API
request timeouts.
-
-
-[Go To TOP](#TOP)
-
-## 1. chat() - AI chat
-
-```python
-def chat(self, keywords: str, model: str = "gpt-4o-mini", timeout: int = 30)
-> str:
- """Initiates a chat session with DuckDuckGo AI.
-
- Args:
- keywords (str): The initial message or question to send to the AI.
- model (str): The model to use: "gpt-4o-mini", "claude-3-haiku",
"llama-3.1-70b", "mixtral-8x7b".
- Defaults to "gpt-4o-mini".
- timeout (int): Timeout value for the HTTP client. Defaults to 30.
-
- Returns:
- str: The response from the AI.
- """
-```
-***Example***
-```python
-results = DDGS().chat("summarize Daniel Defoe's The Consolidator",
model='claude-3-haiku')
-```
+- `ConversationLimitException`: Inherits from DuckDuckGoSearchException,
raised for conversation limit during API requests to AI endpoint.
[Go To TOP](#TOP)
-## 2. text() - text search by duckduckgo.com
+## 1. text() - text search by duckduckgo.com
```python
def text(
@@ -276,12 +261,10 @@
region: wt-wt, us-en, uk-en, ru-ru, etc. Defaults to "wt-wt".
safesearch: on, moderate, off. Defaults to "moderate".
timelimit: d, w, m, y. Defaults to None.
- backend: auto, api, html, lite. Defaults to auto.
+ backend: auto, html, lite. Defaults to auto.
auto - try all backends in random order,
- api - collect data from https://duckduckgo.com,
html - collect data from https://html.duckduckgo.com,
- lite - collect data from https://lite.duckduckgo.com,
- ecosia - collect data from https://www.ecosia.com.
+ lite - collect data from https://lite.duckduckgo.com.
max_results: max number of results. If None, returns results only from
the first response. Defaults to None.
Returns:
@@ -305,7 +288,7 @@
[Go To TOP](#TOP)
-## 3. images() - image search by duckduckgo.com
+## 2. images() - image search by duckduckgo.com
```python
def images(
@@ -372,7 +355,7 @@
[Go To TOP](#TOP)
-## 4. videos() - video search by duckduckgo.com
+## 3. videos() - video search by duckduckgo.com
```python
def videos(
@@ -439,7 +422,7 @@
[Go To TOP](#TOP)
-## 5. news() - news search by duckduckgo.com
+## 4. news() - news search by duckduckgo.com
```python
def news(
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/duckduckgo_search-7.1.1/duckduckgo_search.egg-info/requires.txt
new/duckduckgo_search-8.0.1/duckduckgo_search.egg-info/requires.txt
--- old/duckduckgo_search-7.1.1/duckduckgo_search.egg-info/requires.txt
2024-12-27 00:22:58.000000000 +0100
+++ new/duckduckgo_search-8.0.1/duckduckgo_search.egg-info/requires.txt
2025-04-17 14:37:28.000000000 +0200
@@ -1,9 +1,9 @@
-click>=8.1.7
-primp>=0.9.2
+click>=8.1.8
+primp>=0.15.0
lxml>=5.3.0
[dev]
-mypy>=1.13.0
+mypy>=1.14.1
pytest>=8.3.4
pytest-dependency>=0.6.0
-ruff>=0.8.3
+ruff>=0.9.2
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/duckduckgo_search-7.1.1/pyproject.toml
new/duckduckgo_search-8.0.1/pyproject.toml
--- old/duckduckgo_search-7.1.1/pyproject.toml 2024-12-27 00:22:48.000000000
+0100
+++ new/duckduckgo_search-8.0.1/pyproject.toml 2025-04-17 14:37:17.000000000
+0200
@@ -6,7 +6,7 @@
name = "duckduckgo_search"
description = "Search for words, documents, images, news, maps and text
translation using the DuckDuckGo.com search engine."
readme = "README.md"
-requires-python = ">=3.8"
+requires-python = ">=3.9"
license = {text = "MIT License"}
keywords = ["python", "duckduckgo"]
authors = [
@@ -18,7 +18,6 @@
"Operating System :: OS Independent",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3 :: Only",
- "Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
@@ -29,8 +28,8 @@
"Topic :: Software Development :: Libraries :: Python Modules",
]
dependencies = [
- "click>=8.1.7",
- "primp>=0.9.2",
+ "click>=8.1.8",
+ "primp>=0.15.0",
"lxml>=5.3.0",
]
dynamic = ["version"]
@@ -46,10 +45,10 @@
[project.optional-dependencies]
dev = [
- "mypy>=1.13.0",
+ "mypy>=1.14.1",
"pytest>=8.3.4",
"pytest-dependency>=0.6.0",
- "ruff>=0.8.3",
+ "ruff>=0.9.2",
]
[tool.ruff]
@@ -67,6 +66,6 @@
]
[tool.mypy]
-python_version = "3.8"
+python_version = "3.9"
strict = true
-exclude = ['cli\.py$', '__main__\.py$', "tests/", "build/"]
+exclude = ["build/"]
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/duckduckgo_search-7.1.1/tests/test_cli.py
new/duckduckgo_search-8.0.1/tests/test_cli.py
--- old/duckduckgo_search-7.1.1/tests/test_cli.py 2024-12-27
00:22:48.000000000 +0100
+++ new/duckduckgo_search-8.0.1/tests/test_cli.py 2025-04-17
14:37:17.000000000 +0200
@@ -1,7 +1,10 @@
+from __future__ import annotations
+
import os
import pathlib
import shutil
import time
+from pathlib import Path
import pytest
from click.testing import CliRunner
@@ -10,76 +13,71 @@
from duckduckgo_search.cli import _download_results, _save_csv, _save_json, cli
runner = CliRunner()
-TEXT_RESULTS = None
-IMAGES_RESULTS = None
+TEXT_RESULTS = []
+IMAGES_RESULTS = []
@pytest.fixture(autouse=True)
-def pause_between_tests():
- time.sleep(1)
+def pause_between_tests() -> None:
+ time.sleep(2)
-def test_version_command():
+def test_version_command() -> None:
result = runner.invoke(cli, ["version"])
assert result.output.strip() == __version__
-def test_chat_command():
- result = runner.invoke(cli, ["chat"])
- assert "chat" in result.output
-
-
-def test_text_command():
+def test_text_command() -> None:
result = runner.invoke(cli, ["text", "-k", "python"])
assert "title" in result.output
-def test_images_command():
+def test_images_command() -> None:
result = runner.invoke(cli, ["images", "-k", "cat"])
assert "title" in result.output
-def test_news_command():
+def test_news_command() -> None:
result = runner.invoke(cli, ["news", "-k", "usa"])
assert "title" in result.output
-def test_videos_command():
+def test_videos_command() -> None:
result = runner.invoke(cli, ["videos", "-k", "dog"])
assert "title" in result.output
@pytest.mark.dependency()
-def test_get_text():
+def test_get_text() -> None:
global TEXT_RESULTS
TEXT_RESULTS = DDGS().text("test")
assert TEXT_RESULTS
@pytest.mark.dependency()
-def test_get_images():
+def test_get_images() -> None:
global IMAGES_RESULTS
IMAGES_RESULTS = DDGS().images("test")
assert IMAGES_RESULTS
[email protected](depends=["test_get_data"])
-def test_save_csv(tmp_path):
[email protected](depends=["test_get_text"])
+def test_save_csv(tmp_path: Path) -> None:
temp_file = tmp_path / "test_csv.csv"
- _save_csv(temp_file, RESULTS)
+ _save_csv(temp_file, TEXT_RESULTS)
assert temp_file.exists()
[email protected](depends=["test_get_data"])
-def test_save_json(tmp_path):
[email protected](depends=["test_get_text"])
+def test_save_json(tmp_path: Path) -> None:
temp_file = tmp_path / "test_json.json"
- _save_json(temp_file, RESULTS)
+ _save_json(temp_file, TEXT_RESULTS)
assert temp_file.exists()
[email protected](depends=["test_get_data"])
-def test_text_download():
[email protected](depends=["test_get_text"])
+def test_text_download() -> None:
pathname = pathlib.Path("text_downloads")
- _download_results(test_text_download, TEXT_RESULTS, function_name="text",
pathname=str(pathname))
+ _download_results(f"{test_text_download}", TEXT_RESULTS,
function_name="text", pathname=str(pathname))
assert pathname.is_dir() and pathname.iterdir()
for file in pathname.iterdir():
assert file.is_file()
@@ -87,9 +85,9 @@
@pytest.mark.dependency(depends=["test_get_images"])
-def test_images_download():
+def test_images_download() -> None:
pathname = pathlib.Path("images_downloads")
- _download_results(test_images_download, IMAGES_RESULTS,
function_name="images", pathname=str(pathname))
+ _download_results(f"{test_images_download}", IMAGES_RESULTS,
function_name="images", pathname=str(pathname))
assert pathname.is_dir() and pathname.iterdir()
for file in pathname.iterdir():
assert file.is_file()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/duckduckgo_search-7.1.1/tests/test_duckduckgo_search.py
new/duckduckgo_search-8.0.1/tests/test_duckduckgo_search.py
--- old/duckduckgo_search-7.1.1/tests/test_duckduckgo_search.py 2024-12-27
00:22:48.000000000 +0100
+++ new/duckduckgo_search-8.0.1/tests/test_duckduckgo_search.py 2025-04-17
14:37:17.000000000 +0200
@@ -5,47 +5,36 @@
@pytest.fixture(autouse=True)
-def pause_between_tests():
- time.sleep(1)
+def pause_between_tests() -> None:
+ time.sleep(2)
-def test_context_manager():
+def test_context_manager() -> None:
with DDGS() as ddgs:
results = ddgs.news("cars", max_results=30)
assert 20 <= len(results) <= 30
[email protected]("model", ["gpt-4o-mini", "claude-3-haiku",
"llama-3.1-70b", "mixtral-8x7b"])
-def test_chat(model):
- results = DDGS().chat("cat", model=model)
- assert len(results) >= 1
-
-
-def test_text_html():
+def test_text_html() -> None:
results = DDGS().text("eagle", backend="html", region="br-pt",
timelimit="y", max_results=20)
assert 15 <= len(results) <= 20
-def test_text_lite():
+def test_text_lite() -> None:
results = DDGS().text("dog", backend="lite", region="br-pt",
timelimit="y", max_results=20)
assert 15 <= len(results) <= 20
-def test_text_ecosia():
- results = DDGS().text("cat", backend="ecosia", region="br-pt",
safesearch="off", max_results=20)
- assert 15 <= len(results) <= 20
-
-
-def test_images():
+def test_images() -> None:
results = DDGS().images("flower", max_results=200)
assert 85 <= len(results) <= 200
-def test_videos():
+def test_videos() -> None:
results = DDGS().videos("sea", max_results=40)
assert 30 <= len(results) <= 40
-def test_news():
+def test_news() -> None:
results = DDGS().news("tesla", max_results=30)
assert 20 <= len(results) <= 30