2025-02-26 10:12:22 - 
=== PROJECT STATEMENT ===
2025-02-26 10:12:22 - ---
description: About this project
globs: 
alwaysApply: false
---
# About this project

`twat-search` is a multi-provider search 

## Development Notes
- Uses `uv` for Python package management
- Quality tools: ruff, mypy, pytest
- Clear provider protocol for adding new search backends
- Strong typing and runtime checks throughout

2025-02-26 10:12:22 - 
=== Current Status ===
2025-02-26 10:12:22 - Error: LOG.md is missing
2025-02-26 10:12:22 - [ 928]  .
├── [  64]  .benchmarks
├── [  96]  .cursor
│   └── [ 192]  rules
│       ├── [ 334]  0project.mdc
│       ├── [ 558]  cleanup.mdc
│       └── [4.6K]  filetree.mdc
├── [  96]  .github
│   └── [ 128]  workflows
│       ├── [2.7K]  push.yml
│       └── [1.4K]  release.yml
├── [3.5K]  .gitignore
├── [ 532]  .pre-commit-config.yaml
├── [  96]  .specstory
│   └── [ 800]  history
│       ├── [2.0K]  .what-is-this.md
│       ├── [ 52K]  2025-02-25_01-58-creating-and-tracking-project-tasks.md
│       ├── [7.4K]  2025-02-25_02-17-project-task-continuation-and-progress-update.md
│       ├── [ 11K]  2025-02-25_02-24-planning-tests-for-twat-search-web-package.md
│       ├── [196K]  2025-02-25_02-27-implementing-tests-for-twat-search-package.md
│       ├── [ 46K]  2025-02-25_02-58-transforming-python-script-into-cli-tool.md
│       ├── [ 93K]  2025-02-25_03-09-generating-a-name-for-the-chat.md
│       ├── [5.5K]  2025-02-25_03-33-untitled.md
│       ├── [ 57K]  2025-02-25_03-54-integrating-search-engines-into-twat-search.md
│       ├── [ 72K]  2025-02-25_04-05-consolidating-you-py-and-youcom-py.md
│       ├── [6.1K]  2025-02-25_04-13-missing-env-api-key-names-in-pplx-py.md
│       ├── [118K]  2025-02-25_04-16-implementing-functions-for-brave-search-engines.md
│       ├── [286K]  2025-02-25_04-48-unifying-search-engine-parameters-in-twat-search.md
│       ├── [ 83K]  2025-02-25_05-36-implementing-duckduckgo-search-engine.md
│       ├── [194K]  2025-02-25_05-43-implementing-the-webscout-search-engine.md
│       ├── [ 23K]  2025-02-25_06-07-implementing-bing-scraper-engine.md
│       ├── [ 15K]  2025-02-25_06-12-continuing-bing-scraper-engine-implementation.md
│       ├── [121K]  2025-02-25_06-34-implementing-safe-import-patterns-in-modules.md
│       ├── [9.9K]  2025-02-25_07-09-refactoring-plan-and-progress-update.md
│       ├── [ 40K]  2025-02-25_07-17-implementing-phase-1-from-todo-md.md
│       ├── [292K]  2025-02-25_07-34-integrating-hasdata-google-serp-apis.md
│       ├── [142K]  2025-02-25_08-19-implementing-search-engines-from-nextengines-md.md
│       └── [ 34K]  2025-02-26_09-54-implementing-plain-option-for-search-commands.md
├── [ 499]  CLEANUP.txt
├── [1.0K]  LICENSE
├── [ 64K]  NEXTENGINES.md
├── [1.2K]  PROGRESS.md
├── [ 21K]  README.md
├── [4.1K]  TODO.md
├── [   7]  VERSION.txt
├── [ 12K]  cleanup.py
├── [ 192]  dist
├── [9.8K]  pyproject.toml
├── [ 128]  src
│   └── [ 256]  twat_search
│       ├── [ 556]  __init__.py
│       ├── [2.0K]  __main__.py
│       └── [ 384]  web
│           ├── [1.6K]  __init__.py
│           ├── [4.8K]  api.py
│           ├── [ 43K]  cli.py
│           ├── [4.3K]  config.py
│           ├── [ 576]  engines
│           │   ├── [4.9K]  __init__.py
│           │   ├── [ 24K]  anywebsearch.py
│           │   ├── [3.7K]  base.py
│           │   ├── [ 11K]  bing_scraper.py
│           │   ├── [7.6K]  brave.py
│           │   ├── [8.2K]  critique.py
│           │   ├── [6.7K]  duckduckgo.py
│           │   ├── [ 12K]  google_scraper.py
│           │   ├── [7.1K]  hasdata.py
│           │   ├── [4.9K]  pplx.py
│           │   ├── [ 25K]  searchit.py
│           │   ├── [6.9K]  serpapi.py
│           │   ├── [7.4K]  tavily.py
│           │   └── [7.3K]  you.py
│           ├── [1.0K]  exceptions.py
│           ├── [1.3K]  models.py
│           └── [1.5K]  utils.py
├── [ 256]  tests
│   ├── [  64]  .benchmarks
│   ├── [2.0K]  conftest.py
│   ├── [ 157]  test_twat_search.py
│   ├── [ 192]  unit
│   │   ├── [  42]  __init__.py
│   │   ├── [1.5K]  mock_engine.py
│   │   └── [ 320]  web
│   │       ├── [  46]  __init__.py
│   │       ├── [ 160]  engines
│   │       │   ├── [  37]  __init__.py
│   │       │   └── [4.3K]  test_base.py
│   │       ├── [5.1K]  test_api.py
│   │       ├── [2.7K]  test_config.py
│   │       ├── [2.0K]  test_exceptions.py
│   │       ├── [4.5K]  test_models.py
│   │       └── [3.5K]  test_utils.py
│   └── [ 160]  web
│       └── [ 10K]  test_bing_scraper.py
└── [ 89K]  twat_search.txt

19 directories, 76 files

2025-02-26 10:12:22 - 
Project structure:
2025-02-26 10:12:22 - [ 928]  .
├── [  64]  .benchmarks
├── [  96]  .cursor
│   └── [ 192]  rules
│       ├── [ 334]  0project.mdc
│       ├── [ 558]  cleanup.mdc
│       └── [4.6K]  filetree.mdc
├── [  96]  .github
│   └── [ 128]  workflows
│       ├── [2.7K]  push.yml
│       └── [1.4K]  release.yml
├── [3.5K]  .gitignore
├── [ 532]  .pre-commit-config.yaml
├── [  96]  .specstory
│   └── [ 800]  history
│       ├── [2.0K]  .what-is-this.md
│       ├── [ 52K]  2025-02-25_01-58-creating-and-tracking-project-tasks.md
│       ├── [7.4K]  2025-02-25_02-17-project-task-continuation-and-progress-update.md
│       ├── [ 11K]  2025-02-25_02-24-planning-tests-for-twat-search-web-package.md
│       ├── [196K]  2025-02-25_02-27-implementing-tests-for-twat-search-package.md
│       ├── [ 46K]  2025-02-25_02-58-transforming-python-script-into-cli-tool.md
│       ├── [ 93K]  2025-02-25_03-09-generating-a-name-for-the-chat.md
│       ├── [5.5K]  2025-02-25_03-33-untitled.md
│       ├── [ 57K]  2025-02-25_03-54-integrating-search-engines-into-twat-search.md
│       ├── [ 72K]  2025-02-25_04-05-consolidating-you-py-and-youcom-py.md
│       ├── [6.1K]  2025-02-25_04-13-missing-env-api-key-names-in-pplx-py.md
│       ├── [118K]  2025-02-25_04-16-implementing-functions-for-brave-search-engines.md
│       ├── [286K]  2025-02-25_04-48-unifying-search-engine-parameters-in-twat-search.md
│       ├── [ 83K]  2025-02-25_05-36-implementing-duckduckgo-search-engine.md
│       ├── [194K]  2025-02-25_05-43-implementing-the-webscout-search-engine.md
│       ├── [ 23K]  2025-02-25_06-07-implementing-bing-scraper-engine.md
│       ├── [ 15K]  2025-02-25_06-12-continuing-bing-scraper-engine-implementation.md
│       ├── [121K]  2025-02-25_06-34-implementing-safe-import-patterns-in-modules.md
│       ├── [9.9K]  2025-02-25_07-09-refactoring-plan-and-progress-update.md
│       ├── [ 40K]  2025-02-25_07-17-implementing-phase-1-from-todo-md.md
│       ├── [292K]  2025-02-25_07-34-integrating-hasdata-google-serp-apis.md
│       ├── [142K]  2025-02-25_08-19-implementing-search-engines-from-nextengines-md.md
│       └── [ 34K]  2025-02-26_09-54-implementing-plain-option-for-search-commands.md
├── [ 499]  CLEANUP.txt
├── [1.0K]  LICENSE
├── [ 64K]  NEXTENGINES.md
├── [1.2K]  PROGRESS.md
├── [ 21K]  README.md
├── [4.1K]  TODO.md
├── [   7]  VERSION.txt
├── [ 12K]  cleanup.py
├── [ 192]  dist
├── [9.8K]  pyproject.toml
├── [ 128]  src
│   └── [ 256]  twat_search
│       ├── [ 556]  __init__.py
│       ├── [2.0K]  __main__.py
│       └── [ 384]  web
│           ├── [1.6K]  __init__.py
│           ├── [4.8K]  api.py
│           ├── [ 43K]  cli.py
│           ├── [4.3K]  config.py
│           ├── [ 576]  engines
│           │   ├── [4.9K]  __init__.py
│           │   ├── [ 24K]  anywebsearch.py
│           │   ├── [3.7K]  base.py
│           │   ├── [ 11K]  bing_scraper.py
│           │   ├── [7.6K]  brave.py
│           │   ├── [8.2K]  critique.py
│           │   ├── [6.7K]  duckduckgo.py
│           │   ├── [ 12K]  google_scraper.py
│           │   ├── [7.1K]  hasdata.py
│           │   ├── [4.9K]  pplx.py
│           │   ├── [ 25K]  searchit.py
│           │   ├── [6.9K]  serpapi.py
│           │   ├── [7.4K]  tavily.py
│           │   └── [7.3K]  you.py
│           ├── [1.0K]  exceptions.py
│           ├── [1.3K]  models.py
│           └── [1.5K]  utils.py
├── [ 256]  tests
│   ├── [  64]  .benchmarks
│   ├── [2.0K]  conftest.py
│   ├── [ 157]  test_twat_search.py
│   ├── [ 192]  unit
│   │   ├── [  42]  __init__.py
│   │   ├── [1.5K]  mock_engine.py
│   │   └── [ 320]  web
│   │       ├── [  46]  __init__.py
│   │       ├── [ 160]  engines
│   │       │   ├── [  37]  __init__.py
│   │       │   └── [4.3K]  test_base.py
│   │       ├── [5.1K]  test_api.py
│   │       ├── [2.7K]  test_config.py
│   │       ├── [2.0K]  test_exceptions.py
│   │       ├── [4.5K]  test_models.py
│   │       └── [3.5K]  test_utils.py
│   └── [ 160]  web
│       └── [ 10K]  test_bing_scraper.py
└── [ 89K]  twat_search.txt

19 directories, 76 files

2025-02-26 10:12:22 - On branch main
Your branch is up to date with 'origin/main'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   .cursor/rules/filetree.mdc
	modified:   CLEANUP.txt
	modified:   pyproject.toml
	modified:   src/twat_search/web/cli.py
	modified:   src/twat_search/web/engines/__init__.py

Untracked files:
  (use "git add <file>..." to include in what will be committed)
	.specstory/history/2025-02-25_08-19-implementing-search-engines-from-nextengines-md.md
	.specstory/history/2025-02-26_09-54-implementing-plain-option-for-search-commands.md
	NEXTENGINES.md
	src/twat_search/web/engines/anywebsearch.py
	src/twat_search/web/engines/google_scraper.py
	src/twat_search/web/engines/searchit.py

no changes added to commit (use "git add" and/or "git commit -a")

2025-02-26 10:12:22 - On branch main
Your branch is up to date with 'origin/main'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   .cursor/rules/filetree.mdc
	modified:   CLEANUP.txt
	modified:   pyproject.toml
	modified:   src/twat_search/web/cli.py
	modified:   src/twat_search/web/engines/__init__.py

Untracked files:
  (use "git add <file>..." to include in what will be committed)
	.specstory/history/2025-02-25_08-19-implementing-search-engines-from-nextengines-md.md
	.specstory/history/2025-02-26_09-54-implementing-plain-option-for-search-commands.md
	NEXTENGINES.md
	src/twat_search/web/engines/anywebsearch.py
	src/twat_search/web/engines/google_scraper.py
	src/twat_search/web/engines/searchit.py

no changes added to commit (use "git add" and/or "git commit -a")

2025-02-26 10:12:22 - 
=== Environment Status ===
2025-02-26 10:12:22 - Setting up virtual environment
2025-02-26 10:12:23 - Virtual environment created and activated
2025-02-26 10:12:23 - Installing package with all extras
2025-02-26 10:12:23 - Setting up virtual environment
2025-02-26 10:12:23 - Virtual environment created and activated
2025-02-26 10:12:26 - Package installed successfully
2025-02-26 10:12:26 - Running code quality checks
2025-02-26 10:12:26 - >>> Running code fixes...
2025-02-26 10:12:26 - src/twat_search/__init__.py:22:19: F401 `.web` imported but unused; consider using `importlib.util.find_spec` to test for availability
   |
20 | # Import submodules if available
21 | try:
22 |     from . import web
   |                   ^^^ F401
23 |
24 |     __all__.append("web")
   |
   = help: Remove unused import: `.web`

src/twat_search/web/__init__.py:20:22: F401 `.api.search` imported but unused; consider using `importlib.util.find_spec` to test for availability
   |
18 | # Import core functionality first
19 | try:
20 |     from .api import search
   |                      ^^^^^^ F401
21 |     from .models import SearchResult
22 |     from .config import Config, EngineConfig
   |
   = help: Remove unused import: `.api.search`

src/twat_search/web/__init__.py:21:25: F401 `.models.SearchResult` imported but unused; consider using `importlib.util.find_spec` to test for availability
   |
19 | try:
20 |     from .api import search
21 |     from .models import SearchResult
   |                         ^^^^^^^^^^^^ F401
22 |     from .config import Config, EngineConfig
   |
   = help: Remove unused import: `.models.SearchResult`

src/twat_search/web/__init__.py:22:25: F401 `.config.Config` imported but unused; consider using `importlib.util.find_spec` to test for availability
   |
20 |     from .api import search
21 |     from .models import SearchResult
22 |     from .config import Config, EngineConfig
   |                         ^^^^^^ F401
23 |
24 |     __all__.extend(["Config", "EngineConfig", "SearchResult", "search"])
   |
   = help: Remove unused import

src/twat_search/web/__init__.py:22:33: F401 `.config.EngineConfig` imported but unused; consider using `importlib.util.find_spec` to test for availability
   |
20 |     from .api import search
21 |     from .models import SearchResult
22 |     from .config import Config, EngineConfig
   |                                 ^^^^^^^^^^^^ F401
23 |
24 |     __all__.extend(["Config", "EngineConfig", "SearchResult", "search"])
   |
   = help: Remove unused import

src/twat_search/web/__init__.py:30:26: F401 `.engines.brave` imported but unused; consider using `importlib.util.find_spec` to test for availability
   |
28 | # Import search engines with try-except blocks to handle optional dependencies
29 | try:
30 |     from .engines import brave, brave_news
   |                          ^^^^^ F401
31 |
32 |     __all__.extend(["brave", "brave_news"])
   |
   = help: Remove unused import

src/twat_search/web/__init__.py:30:33: F401 `.engines.brave_news` imported but unused; consider using `importlib.util.find_spec` to test for availability
   |
28 | # Import search engines with try-except blocks to handle optional dependencies
29 | try:
30 |     from .engines import brave, brave_news
   |                                 ^^^^^^^^^^ F401
31 |
32 |     __all__.extend(["brave", "brave_news"])
   |
   = help: Remove unused import

src/twat_search/web/__init__.py:37:26: F401 `.engines.pplx` imported but unused; consider using `importlib.util.find_spec` to test for availability
   |
36 | try:
37 |     from .engines import pplx
   |                          ^^^^ F401
38 |
39 |     __all__.extend(["pplx"])
   |
   = help: Remove unused import: `.engines.pplx`

src/twat_search/web/__init__.py:44:26: F401 `.engines.serpapi` imported but unused; consider using `importlib.util.find_spec` to test for availability
   |
43 | try:
44 |     from .engines import serpapi
   |                          ^^^^^^^ F401
45 |
46 |     __all__.extend(["serpapi"])
   |
   = help: Remove unused import: `.engines.serpapi`

src/twat_search/web/__init__.py:51:26: F401 `.engines.tavily` imported but unused; consider using `importlib.util.find_spec` to test for availability
   |
50 | try:
51 |     from .engines import tavily
   |                          ^^^^^^ F401
52 |
53 |     __all__.extend(["tavily"])
   |
   = help: Remove unused import: `.engines.tavily`

src/twat_search/web/__init__.py:58:26: F401 `.engines.you` imported but unused; consider using `importlib.util.find_spec` to test for availability
   |
57 | try:
58 |     from .engines import you, you_news
   |                          ^^^ F401
59 |
60 |     __all__.extend(["you", "you_news"])
   |
   = help: Remove unused import

src/twat_search/web/__init__.py:58:31: F401 `.engines.you_news` imported but unused; consider using `importlib.util.find_spec` to test for availability
   |
57 | try:
58 |     from .engines import you, you_news
   |                               ^^^^^^^^ F401
59 |
60 |     __all__.extend(["you", "you_news"])
   |
   = help: Remove unused import

src/twat_search/web/__init__.py:65:26: F401 `.engines.critique` imported but unused; consider using `importlib.util.find_spec` to test for availability
   |
64 | try:
65 |     from .engines import critique
   |                          ^^^^^^^^ F401
66 |
67 |     __all__.extend(["critique"])
   |
   = help: Remove unused import: `.engines.critique`

src/twat_search/web/__init__.py:72:26: F401 `.engines.duckduckgo` imported but unused; consider using `importlib.util.find_spec` to test for availability
   |
71 | try:
72 |     from .engines import duckduckgo
   |                          ^^^^^^^^^^ F401
73 |
74 |     __all__.extend(["duckduckgo"])
   |
   = help: Remove unused import: `.engines.duckduckgo`

src/twat_search/web/__init__.py:79:26: F401 `.engines.bing_scraper` imported but unused; consider using `importlib.util.find_spec` to test for availability
   |
78 | try:
79 |     from .engines import bing_scraper
   |                          ^^^^^^^^^^^^ F401
80 |
81 |     __all__.extend(["bing_scraper"])
   |
   = help: Remove unused import: `.engines.bing_scraper`

src/twat_search/web/api.py:35:5: PLR0913 Too many arguments in function definition (6 > 5)
   |
35 | def init_engine_task(
   |     ^^^^^^^^^^^^^^^^ PLR0913
36 |     engine_name: str,
37 |     config: Config,
   |

src/twat_search/web/api.py:69:11: PLR0913 Too many arguments in function definition (8 > 5)
   |
69 | async def search(
   |           ^^^^^^ PLR0913
70 |     query: str,
71 |     engines: list[str] | None = None,
   |

src/twat_search/web/api.py:75:5: FBT001 Boolean-typed positional argument in function definition
   |
73 |     country: str | None = None,
74 |     language: str | None = None,
75 |     safe_search: bool = True,
   |     ^^^^^^^^^^^ FBT001
76 |     time_frame: str | None = None,
77 |     config: Optional["Config"] = None,
   |

src/twat_search/web/api.py:75:5: FBT002 Boolean default positional argument in function definition
   |
73 |     country: str | None = None,
74 |     language: str | None = None,
75 |     safe_search: bool = True,
   |     ^^^^^^^^^^^ FBT002
76 |     time_frame: str | None = None,
77 |     config: Optional["Config"] = None,
   |

src/twat_search/web/cli.py:92:34: FBT001 Boolean-typed positional argument in function definition
   |
90 |             )
91 |
92 |     def _configure_logging(self, verbose: bool = False) -> None:
   |                                  ^^^^^^^ FBT001
93 |         """Configure the logging level based on the verbose flag."""
94 |         # When not in verbose mode, silence almost all logs
   |

src/twat_search/web/cli.py:92:34: FBT002 Boolean default positional argument in function definition
   |
90 |             )
91 |
92 |     def _configure_logging(self, verbose: bool = False) -> None:
   |                                  ^^^^^^^ FBT002
93 |         """Configure the logging level based on the verbose flag."""
94 |         # When not in verbose mode, silence almost all logs
   |

src/twat_search/web/cli.py:189:50: PLR2004 Magic value used in comparison, consider replacing `100` with a constant variable
    |
187 |                         "title": result.title,
188 |                         "snippet": result.snippet[:100] + "..."
189 |                         if len(result.snippet) > 100
    |                                                  ^^^ PLR2004
190 |                         else result.snippet,
191 |                         "raw_result": getattr(result, "raw", None),
    |

src/twat_search/web/cli.py:197:9: C901 `_display_results` is too complex (14 > 10)
    |
195 |         return processed
196 |
197 |     def _display_results(
    |         ^^^^^^^^^^^^^^^^ C901
198 |         self,
199 |         processed_results: list[dict[str, Any]],
    |

src/twat_search/web/cli.py:197:9: PLR0912 Too many branches (14 > 12)
    |
195 |         return processed
196 |
197 |     def _display_results(
    |         ^^^^^^^^^^^^^^^^ PLR0912
198 |         self,
199 |         processed_results: list[dict[str, Any]],
    |

src/twat_search/web/cli.py:200:9: FBT001 Boolean-typed positional argument in function definition
    |
198 |         self,
199 |         processed_results: list[dict[str, Any]],
200 |         verbose: bool = False,
    |         ^^^^^^^ FBT001
201 |         plain: bool = False,
202 |     ) -> None:
    |

src/twat_search/web/cli.py:200:9: FBT002 Boolean default positional argument in function definition
    |
198 |         self,
199 |         processed_results: list[dict[str, Any]],
200 |         verbose: bool = False,
    |         ^^^^^^^ FBT002
201 |         plain: bool = False,
202 |     ) -> None:
    |

src/twat_search/web/cli.py:201:9: FBT001 Boolean-typed positional argument in function definition
    |
199 |         processed_results: list[dict[str, Any]],
200 |         verbose: bool = False,
201 |         plain: bool = False,
    |         ^^^^^ FBT001
202 |     ) -> None:
203 |         if not processed_results:
    |

src/twat_search/web/cli.py:201:9: FBT002 Boolean default positional argument in function definition
    |
199 |         processed_results: list[dict[str, Any]],
200 |         verbose: bool = False,
201 |         plain: bool = False,
    |         ^^^^^ FBT002
202 |     ) -> None:
203 |         if not processed_results:
    |

src/twat_search/web/cli.py:288:15: PLR0913 Too many arguments in function definition (6 > 5)
    |
286 |         console.print(table)
287 |
288 |     async def _search_engine(
    |               ^^^^^^^^^^^^^^ PLR0913
289 |         self,
290 |         engine: str,
    |

src/twat_search/web/cli.py:293:9: FBT001 Boolean-typed positional argument in function definition
    |
291 |         query: str,
292 |         params: dict,
293 |         json: bool,
    |         ^^^^ FBT001
294 |         verbose: bool,
295 |         plain: bool = False,
    |

src/twat_search/web/cli.py:294:9: FBT001 Boolean-typed positional argument in function definition
    |
292 |         params: dict,
293 |         json: bool,
294 |         verbose: bool,
    |         ^^^^^^^ FBT001
295 |         plain: bool = False,
296 |     ) -> list[dict[str, Any]]:
    |

src/twat_search/web/cli.py:295:9: FBT001 Boolean-typed positional argument in function definition
    |
293 |         json: bool,
294 |         verbose: bool,
295 |         plain: bool = False,
    |         ^^^^^ FBT001
296 |     ) -> list[dict[str, Any]]:
297 |         """
    |

src/twat_search/web/cli.py:295:9: FBT002 Boolean default positional argument in function definition
    |
293 |         json: bool,
294 |         verbose: bool,
295 |         plain: bool = False,
    |         ^^^^^ FBT002
296 |     ) -> list[dict[str, Any]]:
297 |         """
    |

src/twat_search/web/cli.py:356:9: PLR0913 Too many arguments in function definition (12 > 5)
    |
354 |             return []
355 |
356 |     def q(
    |         ^ PLR0913
357 |         self,
358 |         query: str,
    |

src/twat_search/web/cli.py:365:9: FBT001 Boolean-typed positional argument in function definition
    |
363 |         country: str | None = None,
364 |         language: str | None = None,
365 |         safe_search: bool = True,
    |         ^^^^^^^^^^^ FBT001
366 |         time_frame: str | None = None,
367 |         verbose: bool = False,
    |

src/twat_search/web/cli.py:365:9: FBT002 Boolean default positional argument in function definition
    |
363 |         country: str | None = None,
364 |         language: str | None = None,
365 |         safe_search: bool = True,
    |         ^^^^^^^^^^^ FBT002
366 |         time_frame: str | None = None,
367 |         verbose: bool = False,
    |

src/twat_search/web/cli.py:367:9: FBT001 Boolean-typed positional argument in function definition
    |
365 |         safe_search: bool = True,
366 |         time_frame: str | None = None,
367 |         verbose: bool = False,
    |         ^^^^^^^ FBT001
368 |         json: bool = False,
369 |         plain: bool = False,
    |

src/twat_search/web/cli.py:367:9: FBT002 Boolean default positional argument in function definition
    |
365 |         safe_search: bool = True,
366 |         time_frame: str | None = None,
367 |         verbose: bool = False,
    |         ^^^^^^^ FBT002
368 |         json: bool = False,
369 |         plain: bool = False,
    |

src/twat_search/web/cli.py:368:9: FBT001 Boolean-typed positional argument in function definition
    |
366 |         time_frame: str | None = None,
367 |         verbose: bool = False,
368 |         json: bool = False,
    |         ^^^^ FBT001
369 |         plain: bool = False,
370 |         **kwargs: Any,
    |

src/twat_search/web/cli.py:368:9: FBT002 Boolean default positional argument in function definition
    |
366 |         time_frame: str | None = None,
367 |         verbose: bool = False,
368 |         json: bool = False,
    |         ^^^^ FBT002
369 |         plain: bool = False,
370 |         **kwargs: Any,
    |

src/twat_search/web/cli.py:369:9: FBT001 Boolean-typed positional argument in function definition
    |
367 |         verbose: bool = False,
368 |         json: bool = False,
369 |         plain: bool = False,
    |         ^^^^^ FBT001
370 |         **kwargs: Any,
371 |     ) -> list[dict[str, Any]]:
    |

src/twat_search/web/cli.py:369:9: FBT002 Boolean default positional argument in function definition
    |
367 |         verbose: bool = False,
368 |         json: bool = False,
369 |         plain: bool = False,
    |         ^^^^^ FBT002
370 |         **kwargs: Any,
371 |     ) -> list[dict[str, Any]]:
    |

src/twat_search/web/cli.py:436:42: FBT001 Boolean-typed positional argument in function definition
    |
435 |     def info(
436 |         self, engine: str | None = None, json: bool = False, plain: bool = False
    |                                          ^^^^ FBT001
437 |     ) -> None:
438 |         """
    |

src/twat_search/web/cli.py:436:42: FBT002 Boolean default positional argument in function definition
    |
435 |     def info(
436 |         self, engine: str | None = None, json: bool = False, plain: bool = False
    |                                          ^^^^ FBT002
437 |     ) -> None:
438 |         """
    |

src/twat_search/web/cli.py:436:62: FBT001 Boolean-typed positional argument in function definition
    |
435 |     def info(
436 |         self, engine: str | None = None, json: bool = False, plain: bool = False
    |                                                              ^^^^^ FBT001
437 |     ) -> None:
438 |         """
    |

src/twat_search/web/cli.py:436:62: FBT002 Boolean default positional argument in function definition
    |
435 |     def info(
436 |         self, engine: str | None = None, json: bool = False, plain: bool = False
    |                                                              ^^^^^ FBT002
437 |     ) -> None:
438 |         """
    |

src/twat_search/web/cli.py:511:9: C901 `_show_engine_details` is too complex (11 > 10)
    |
509 |         )
510 |
511 |     def _show_engine_details(self, engine_name: str, config: "Config") -> None:
    |         ^^^^^^^^^^^^^^^^^^^^ C901
512 |         if engine_name not in config.engines:
513 |             self.console.print(
    |

src/twat_search/web/cli.py:644:15: PLR0913 Too many arguments in function definition (14 > 5)
    |
642 |         return is_engine_available(engine_name)
643 |
644 |     async def critique(
    |               ^^^^^^^^ PLR0913
645 |         self,
646 |         query: str,
    |

src/twat_search/web/cli.py:650:9: FBT002 Boolean default positional argument in function definition
    |
648 |         country: str | None = None,
649 |         language: str | None = None,
650 |         safe_search: bool | None = True,
    |         ^^^^^^^^^^^ FBT002
651 |         time_frame: str | None = None,
652 |         image_url: str | None = None,
    |

src/twat_search/web/cli.py:657:9: FBT001 Boolean-typed positional argument in function definition
    |
655 |         source_blacklist: str | None = None,
656 |         api_key: str | None = None,
657 |         verbose: bool = False,
    |         ^^^^^^^ FBT001
658 |         json: bool = False,
659 |         plain: bool = False,
    |

src/twat_search/web/cli.py:657:9: FBT002 Boolean default positional argument in function definition
    |
655 |         source_blacklist: str | None = None,
656 |         api_key: str | None = None,
657 |         verbose: bool = False,
    |         ^^^^^^^ FBT002
658 |         json: bool = False,
659 |         plain: bool = False,
    |

src/twat_search/web/cli.py:658:9: FBT001 Boolean-typed positional argument in function definition
    |
656 |         api_key: str | None = None,
657 |         verbose: bool = False,
658 |         json: bool = False,
    |         ^^^^ FBT001
659 |         plain: bool = False,
660 |         **kwargs: Any,
    |

src/twat_search/web/cli.py:658:9: FBT002 Boolean default positional argument in function definition
    |
656 |         api_key: str | None = None,
657 |         verbose: bool = False,
658 |         json: bool = False,
    |         ^^^^ FBT002
659 |         plain: bool = False,
660 |         **kwargs: Any,
    |

src/twat_search/web/cli.py:659:9: FBT001 Boolean-typed positional argument in function definition
    |
657 |         verbose: bool = False,
658 |         json: bool = False,
659 |         plain: bool = False,
    |         ^^^^^ FBT001
660 |         **kwargs: Any,
661 |     ) -> list[dict[str, Any]]:
    |

src/twat_search/web/cli.py:659:9: FBT002 Boolean default positional argument in function definition
    |
657 |         verbose: bool = False,
658 |         json: bool = False,
659 |         plain: bool = False,
    |         ^^^^^ FBT002
660 |         **kwargs: Any,
661 |     ) -> list[dict[str, Any]]:
    |

src/twat_search/web/cli.py:718:15: PLR0913 Too many arguments in function definition (10 > 5)
    |
716 |         )
717 |
718 |     async def brave(
    |               ^^^^^ PLR0913
719 |         self,
720 |         query: str,
    |

src/twat_search/web/cli.py:724:9: FBT002 Boolean default positional argument in function definition
    |
722 |         country: str | None = None,
723 |         language: str | None = None,
724 |         safe_search: bool | None = True,
    |         ^^^^^^^^^^^ FBT002
725 |         time_frame: str | None = None,
726 |         api_key: str | None = None,
    |

src/twat_search/web/cli.py:727:9: FBT001 Boolean-typed positional argument in function definition
    |
725 |         time_frame: str | None = None,
726 |         api_key: str | None = None,
727 |         verbose: bool = False,
    |         ^^^^^^^ FBT001
728 |         json: bool = False,
729 |         plain: bool = False,
    |

src/twat_search/web/cli.py:727:9: FBT002 Boolean default positional argument in function definition
    |
725 |         time_frame: str | None = None,
726 |         api_key: str | None = None,
727 |         verbose: bool = False,
    |         ^^^^^^^ FBT002
728 |         json: bool = False,
729 |         plain: bool = False,
    |

src/twat_search/web/cli.py:728:9: FBT001 Boolean-typed positional argument in function definition
    |
726 |         api_key: str | None = None,
727 |         verbose: bool = False,
728 |         json: bool = False,
    |         ^^^^ FBT001
729 |         plain: bool = False,
730 |         **kwargs: Any,
    |

src/twat_search/web/cli.py:728:9: FBT002 Boolean default positional argument in function definition
    |
726 |         api_key: str | None = None,
727 |         verbose: bool = False,
728 |         json: bool = False,
    |         ^^^^ FBT002
729 |         plain: bool = False,
730 |         **kwargs: Any,
    |

src/twat_search/web/cli.py:729:9: FBT001 Boolean-typed positional argument in function definition
    |
727 |         verbose: bool = False,
728 |         json: bool = False,
729 |         plain: bool = False,
    |         ^^^^^ FBT001
730 |         **kwargs: Any,
731 |     ) -> list[dict[str, Any]]:
    |

src/twat_search/web/cli.py:729:9: FBT002 Boolean default positional argument in function definition
    |
727 |         verbose: bool = False,
728 |         json: bool = False,
729 |         plain: bool = False,
    |         ^^^^^ FBT002
730 |         **kwargs: Any,
731 |     ) -> list[dict[str, Any]]:
    |

src/twat_search/web/cli.py:766:15: PLR0913 Too many arguments in function definition (10 > 5)
    |
764 |         return await self._search_engine("brave", query, params, json, verbose, plain)
765 |
766 |     async def brave_news(
    |               ^^^^^^^^^^ PLR0913
767 |         self,
768 |         query: str,
    |

src/twat_search/web/cli.py:772:9: FBT002 Boolean default positional argument in function definition
    |
770 |         country: str | None = None,
771 |         language: str | None = None,
772 |         safe_search: bool | None = True,
    |         ^^^^^^^^^^^ FBT002
773 |         time_frame: str | None = None,
774 |         api_key: str | None = None,
    |

src/twat_search/web/cli.py:775:9: FBT001 Boolean-typed positional argument in function definition
    |
773 |         time_frame: str | None = None,
774 |         api_key: str | None = None,
775 |         verbose: bool = False,
    |         ^^^^^^^ FBT001
776 |         json: bool = False,
777 |         plain: bool = False,
    |

src/twat_search/web/cli.py:775:9: FBT002 Boolean default positional argument in function definition
    |
773 |         time_frame: str | None = None,
774 |         api_key: str | None = None,
775 |         verbose: bool = False,
    |         ^^^^^^^ FBT002
776 |         json: bool = False,
777 |         plain: bool = False,
    |

src/twat_search/web/cli.py:776:9: FBT001 Boolean-typed positional argument in function definition
    |
774 |         api_key: str | None = None,
775 |         verbose: bool = False,
776 |         json: bool = False,
    |         ^^^^ FBT001
777 |         plain: bool = False,
778 |         **kwargs: Any,
    |

src/twat_search/web/cli.py:776:9: FBT002 Boolean default positional argument in function definition
    |
774 |         api_key: str | None = None,
775 |         verbose: bool = False,
776 |         json: bool = False,
    |         ^^^^ FBT002
777 |         plain: bool = False,
778 |         **kwargs: Any,
    |

src/twat_search/web/cli.py:777:9: FBT001 Boolean-typed positional argument in function definition
    |
775 |         verbose: bool = False,
776 |         json: bool = False,
777 |         plain: bool = False,
    |         ^^^^^ FBT001
778 |         **kwargs: Any,
779 |     ) -> list[dict[str, Any]]:
    |

src/twat_search/web/cli.py:777:9: FBT002 Boolean default positional argument in function definition
    |
775 |         verbose: bool = False,
776 |         json: bool = False,
777 |         plain: bool = False,
    |         ^^^^^ FBT002
778 |         **kwargs: Any,
779 |     ) -> list[dict[str, Any]]:
    |

src/twat_search/web/cli.py:816:15: PLR0913 Too many arguments in function definition (10 > 5)
    |
814 |         )
815 |
816 |     async def serpapi(
    |               ^^^^^^^ PLR0913
817 |         self,
818 |         query: str,
    |

src/twat_search/web/cli.py:822:9: FBT002 Boolean default positional argument in function definition
    |
820 |         country: str | None = None,
821 |         language: str | None = None,
822 |         safe_search: bool | None = True,
    |         ^^^^^^^^^^^ FBT002
823 |         time_frame: str | None = None,
824 |         api_key: str | None = None,
    |

src/twat_search/web/cli.py:825:9: FBT001 Boolean-typed positional argument in function definition
    |
823 |         time_frame: str | None = None,
824 |         api_key: str | None = None,
825 |         verbose: bool = False,
    |         ^^^^^^^ FBT001
826 |         json: bool = False,
827 |         plain: bool = False,
    |

src/twat_search/web/cli.py:825:9: FBT002 Boolean default positional argument in function definition
    |
823 |         time_frame: str | None = None,
824 |         api_key: str | None = None,
825 |         verbose: bool = False,
    |         ^^^^^^^ FBT002
826 |         json: bool = False,
827 |         plain: bool = False,
    |

src/twat_search/web/cli.py:826:9: FBT001 Boolean-typed positional argument in function definition
    |
824 |         api_key: str | None = None,
825 |         verbose: bool = False,
826 |         json: bool = False,
    |         ^^^^ FBT001
827 |         plain: bool = False,
828 |         **kwargs: Any,
    |

src/twat_search/web/cli.py:826:9: FBT002 Boolean default positional argument in function definition
    |
824 |         api_key: str | None = None,
825 |         verbose: bool = False,
826 |         json: bool = False,
    |         ^^^^ FBT002
827 |         plain: bool = False,
828 |         **kwargs: Any,
    |

src/twat_search/web/cli.py:827:9: FBT001 Boolean-typed positional argument in function definition
    |
825 |         verbose: bool = False,
826 |         json: bool = False,
827 |         plain: bool = False,
    |         ^^^^^ FBT001
828 |         **kwargs: Any,
829 |     ) -> list[dict[str, Any]]:
    |

src/twat_search/web/cli.py:827:9: FBT002 Boolean default positional argument in function definition
    |
825 |         verbose: bool = False,
826 |         json: bool = False,
827 |         plain: bool = False,
    |         ^^^^^ FBT002
828 |         **kwargs: Any,
829 |     ) -> list[dict[str, Any]]:
    |

src/twat_search/web/cli.py:864:15: PLR0913 Too many arguments in function definition (13 > 5)
    |
862 |         return await self._search_engine("serpapi", query, params, json, verbose, plain)
863 |
864 |     async def tavily(
    |               ^^^^^^ PLR0913
865 |         self,
866 |         query: str,
    |

src/twat_search/web/cli.py:870:9: FBT002 Boolean default positional argument in function definition
    |
868 |         country: str | None = None,
869 |         language: str | None = None,
870 |         safe_search: bool | None = True,
    |         ^^^^^^^^^^^ FBT002
871 |         time_frame: str | None = None,
872 |         api_key: str | None = None,
    |

src/twat_search/web/cli.py:876:9: FBT001 Boolean-typed positional argument in function definition
    |
874 |         include_domains: str | None = None,
875 |         exclude_domains: str | None = None,
876 |         verbose: bool = False,
    |         ^^^^^^^ FBT001
877 |         json: bool = False,
878 |         plain: bool = False,
    |

src/twat_search/web/cli.py:876:9: FBT002 Boolean default positional argument in function definition
    |
874 |         include_domains: str | None = None,
875 |         exclude_domains: str | None = None,
876 |         verbose: bool = False,
    |         ^^^^^^^ FBT002
877 |         json: bool = False,
878 |         plain: bool = False,
    |

src/twat_search/web/cli.py:877:9: FBT001 Boolean-typed positional argument in function definition
    |
875 |         exclude_domains: str | None = None,
876 |         verbose: bool = False,
877 |         json: bool = False,
    |         ^^^^ FBT001
878 |         plain: bool = False,
879 |         **kwargs: Any,
    |

src/twat_search/web/cli.py:877:9: FBT002 Boolean default positional argument in function definition
    |
875 |         exclude_domains: str | None = None,
876 |         verbose: bool = False,
877 |         json: bool = False,
    |         ^^^^ FBT002
878 |         plain: bool = False,
879 |         **kwargs: Any,
    |

src/twat_search/web/cli.py:878:9: FBT001 Boolean-typed positional argument in function definition
    |
876 |         verbose: bool = False,
877 |         json: bool = False,
878 |         plain: bool = False,
    |         ^^^^^ FBT001
879 |         **kwargs: Any,
880 |     ) -> list[dict[str, Any]]:
    |

src/twat_search/web/cli.py:878:9: FBT002 Boolean default positional argument in function definition
    |
876 |         verbose: bool = False,
877 |         json: bool = False,
878 |         plain: bool = False,
    |         ^^^^^ FBT002
879 |         **kwargs: Any,
880 |     ) -> list[dict[str, Any]]:
    |

src/twat_search/web/cli.py:926:15: PLR0913 Too many arguments in function definition (11 > 5)
    |
924 |         return await self._search_engine("tavily", query, params, json, verbose, plain)
925 |
926 |     async def pplx(
    |               ^^^^ PLR0913
927 |         self,
928 |         query: str,
    |

src/twat_search/web/cli.py:932:9: FBT002 Boolean default positional argument in function definition
    |
930 |         country: str | None = None,
931 |         language: str | None = None,
932 |         safe_search: bool | None = True,
    |         ^^^^^^^^^^^ FBT002
933 |         time_frame: str | None = None,
934 |         api_key: str | None = None,
    |

src/twat_search/web/cli.py:936:9: FBT001 Boolean-typed positional argument in function definition
    |
934 |         api_key: str | None = None,
935 |         model: str | None = None,
936 |         verbose: bool = False,
    |         ^^^^^^^ FBT001
937 |         json: bool = False,
938 |         plain: bool = False,
    |

src/twat_search/web/cli.py:936:9: FBT002 Boolean default positional argument in function definition
    |
934 |         api_key: str | None = None,
935 |         model: str | None = None,
936 |         verbose: bool = False,
    |         ^^^^^^^ FBT002
937 |         json: bool = False,
938 |         plain: bool = False,
    |

src/twat_search/web/cli.py:937:9: FBT001 Boolean-typed positional argument in function definition
    |
935 |         model: str | None = None,
936 |         verbose: bool = False,
937 |         json: bool = False,
    |         ^^^^ FBT001
938 |         plain: bool = False,
939 |         **kwargs: Any,
    |

src/twat_search/web/cli.py:937:9: FBT002 Boolean default positional argument in function definition
    |
935 |         model: str | None = None,
936 |         verbose: bool = False,
937 |         json: bool = False,
    |         ^^^^ FBT002
938 |         plain: bool = False,
939 |         **kwargs: Any,
    |

src/twat_search/web/cli.py:938:9: FBT001 Boolean-typed positional argument in function definition
    |
936 |         verbose: bool = False,
937 |         json: bool = False,
938 |         plain: bool = False,
    |         ^^^^^ FBT001
939 |         **kwargs: Any,
940 |     ) -> list[dict[str, Any]]:
    |

src/twat_search/web/cli.py:938:9: FBT002 Boolean default positional argument in function definition
    |
936 |         verbose: bool = False,
937 |         json: bool = False,
938 |         plain: bool = False,
    |         ^^^^^ FBT002
939 |         **kwargs: Any,
940 |     ) -> list[dict[str, Any]]:
    |

src/twat_search/web/cli.py:976:15: PLR0913 Too many arguments in function definition (10 > 5)
    |
974 |         return await self._search_engine("pplx", query, params, json, verbose, plain)
975 |
976 |     async def you(
    |               ^^^ PLR0913
977 |         self,
978 |         query: str,
    |

src/twat_search/web/cli.py:982:9: FBT002 Boolean default positional argument in function definition
    |
980 |         country: str | None = None,
981 |         language: str | None = None,
982 |         safe_search: bool | None = True,
    |         ^^^^^^^^^^^ FBT002
983 |         time_frame: str | None = None,
984 |         api_key: str | None = None,
    |

src/twat_search/web/cli.py:985:9: FBT001 Boolean-typed positional argument in function definition
    |
983 |         time_frame: str | None = None,
984 |         api_key: str | None = None,
985 |         verbose: bool = False,
    |         ^^^^^^^ FBT001
986 |         json: bool = False,
987 |         plain: bool = False,
    |

src/twat_search/web/cli.py:985:9: FBT002 Boolean default positional argument in function definition
    |
983 |         time_frame: str | None = None,
984 |         api_key: str | None = None,
985 |         verbose: bool = False,
    |         ^^^^^^^ FBT002
986 |         json: bool = False,
987 |         plain: bool = False,
    |

src/twat_search/web/cli.py:986:9: FBT001 Boolean-typed positional argument in function definition
    |
984 |         api_key: str | None = None,
985 |         verbose: bool = False,
986 |         json: bool = False,
    |         ^^^^ FBT001
987 |         plain: bool = False,
988 |         **kwargs: Any,
    |

src/twat_search/web/cli.py:986:9: FBT002 Boolean default positional argument in function definition
    |
984 |         api_key: str | None = None,
985 |         verbose: bool = False,
986 |         json: bool = False,
    |         ^^^^ FBT002
987 |         plain: bool = False,
988 |         **kwargs: Any,
    |

src/twat_search/web/cli.py:987:9: FBT001 Boolean-typed positional argument in function definition
    |
985 |         verbose: bool = False,
986 |         json: bool = False,
987 |         plain: bool = False,
    |         ^^^^^ FBT001
988 |         **kwargs: Any,
989 |     ) -> list[dict[str, Any]]:
    |

src/twat_search/web/cli.py:987:9: FBT002 Boolean default positional argument in function definition
    |
985 |         verbose: bool = False,
986 |         json: bool = False,
987 |         plain: bool = False,
    |         ^^^^^ FBT002
988 |         **kwargs: Any,
989 |     ) -> list[dict[str, Any]]:
    |

src/twat_search/web/cli.py:1023:15: PLR0913 Too many arguments in function definition (10 > 5)
     |
1021 |         return await self._search_engine("you", query, params, json, verbose, plain)
1022 |
1023 |     async def you_news(
     |               ^^^^^^^^ PLR0913
1024 |         self,
1025 |         query: str,
     |

src/twat_search/web/cli.py:1029:9: FBT002 Boolean default positional argument in function definition
     |
1027 |         country: str | None = None,
1028 |         language: str | None = None,
1029 |         safe_search: bool | None = True,
     |         ^^^^^^^^^^^ FBT002
1030 |         time_frame: str | None = None,
1031 |         api_key: str | None = None,
     |

src/twat_search/web/cli.py:1032:9: FBT001 Boolean-typed positional argument in function definition
     |
1030 |         time_frame: str | None = None,
1031 |         api_key: str | None = None,
1032 |         verbose: bool = False,
     |         ^^^^^^^ FBT001
1033 |         json: bool = False,
1034 |         plain: bool = False,
     |

src/twat_search/web/cli.py:1032:9: FBT002 Boolean default positional argument in function definition
     |
1030 |         time_frame: str | None = None,
1031 |         api_key: str | None = None,
1032 |         verbose: bool = False,
     |         ^^^^^^^ FBT002
1033 |         json: bool = False,
1034 |         plain: bool = False,
     |

src/twat_search/web/cli.py:1033:9: FBT001 Boolean-typed positional argument in function definition
     |
1031 |         api_key: str | None = None,
1032 |         verbose: bool = False,
1033 |         json: bool = False,
     |         ^^^^ FBT001
1034 |         plain: bool = False,
1035 |         **kwargs: Any,
     |

src/twat_search/web/cli.py:1033:9: FBT002 Boolean default positional argument in function definition
     |
1031 |         api_key: str | None = None,
1032 |         verbose: bool = False,
1033 |         json: bool = False,
     |         ^^^^ FBT002
1034 |         plain: bool = False,
1035 |         **kwargs: Any,
     |

src/twat_search/web/cli.py:1034:9: FBT001 Boolean-typed positional argument in function definition
     |
1032 |         verbose: bool = False,
1033 |         json: bool = False,
1034 |         plain: bool = False,
     |         ^^^^^ FBT001
1035 |         **kwargs: Any,
1036 |     ) -> list[dict[str, Any]]:
     |

src/twat_search/web/cli.py:1034:9: FBT002 Boolean default positional argument in function definition
     |
1032 |         verbose: bool = False,
1033 |         json: bool = False,
1034 |         plain: bool = False,
     |         ^^^^^ FBT002
1035 |         **kwargs: Any,
1036 |     ) -> list[dict[str, Any]]:
     |

src/twat_search/web/cli.py:1072:15: PLR0913 Too many arguments in function definition (11 > 5)
     |
1070 |         )
1071 |
1072 |     async def duckduckgo(
     |               ^^^^^^^^^^ PLR0913
1073 |         self,
1074 |         query: str,
     |

src/twat_search/web/cli.py:1077:9: ARG002 Unused method argument: `language`
     |
1075 |         num_results: int = 5,
1076 |         country: str | None = None,
1077 |         language: str | None = None,
     |         ^^^^^^^^ ARG002
1078 |         safe_search: bool | None = True,
1079 |         time_frame: str | None = None,
     |

src/twat_search/web/cli.py:1078:9: FBT002 Boolean default positional argument in function definition
     |
1076 |         country: str | None = None,
1077 |         language: str | None = None,
1078 |         safe_search: bool | None = True,
     |         ^^^^^^^^^^^ FBT002
1079 |         time_frame: str | None = None,
1080 |         proxy: str | None = None,
     |

src/twat_search/web/cli.py:1082:9: FBT001 Boolean-typed positional argument in function definition
     |
1080 |         proxy: str | None = None,
1081 |         timeout: int = 10,
1082 |         verbose: bool = False,
     |         ^^^^^^^ FBT001
1083 |         json: bool = False,
1084 |         plain: bool = False,
     |

src/twat_search/web/cli.py:1082:9: FBT002 Boolean default positional argument in function definition
     |
1080 |         proxy: str | None = None,
1081 |         timeout: int = 10,
1082 |         verbose: bool = False,
     |         ^^^^^^^ FBT002
1083 |         json: bool = False,
1084 |         plain: bool = False,
     |

src/twat_search/web/cli.py:1083:9: FBT001 Boolean-typed positional argument in function definition
     |
1081 |         timeout: int = 10,
1082 |         verbose: bool = False,
1083 |         json: bool = False,
     |         ^^^^ FBT001
1084 |         plain: bool = False,
1085 |         **kwargs: Any,
     |

src/twat_search/web/cli.py:1083:9: FBT002 Boolean default positional argument in function definition
     |
1081 |         timeout: int = 10,
1082 |         verbose: bool = False,
1083 |         json: bool = False,
     |         ^^^^ FBT002
1084 |         plain: bool = False,
1085 |         **kwargs: Any,
     |

src/twat_search/web/cli.py:1084:9: FBT001 Boolean-typed positional argument in function definition
     |
1082 |         verbose: bool = False,
1083 |         json: bool = False,
1084 |         plain: bool = False,
     |         ^^^^^ FBT001
1085 |         **kwargs: Any,
1086 |     ) -> list[dict[str, Any]]:
     |

src/twat_search/web/cli.py:1084:9: FBT002 Boolean default positional argument in function definition
     |
1082 |         verbose: bool = False,
1083 |         json: bool = False,
1084 |         plain: bool = False,
     |         ^^^^^ FBT002
1085 |         **kwargs: Any,
1086 |     ) -> list[dict[str, Any]]:
     |

src/twat_search/web/cli.py:1123:15: PLR0913 Too many arguments in function definition (8 > 5)
     |
1121 |         )
1122 |
1123 |     async def hasdata_google(
     |               ^^^^^^^^^^^^^^ PLR0913
1124 |         self,
1125 |         query: str,
     |

src/twat_search/web/cli.py:1130:9: FBT001 Boolean-typed positional argument in function definition
     |
1128 |         device_type: str = "desktop",
1129 |         api_key: str | None = None,
1130 |         verbose: bool = False,
     |         ^^^^^^^ FBT001
1131 |         json: bool = False,
1132 |         plain: bool = False,
     |

src/twat_search/web/cli.py:1130:9: FBT002 Boolean default positional argument in function definition
     |
1128 |         device_type: str = "desktop",
1129 |         api_key: str | None = None,
1130 |         verbose: bool = False,
     |         ^^^^^^^ FBT002
1131 |         json: bool = False,
1132 |         plain: bool = False,
     |

src/twat_search/web/cli.py:1131:9: FBT001 Boolean-typed positional argument in function definition
     |
1129 |         api_key: str | None = None,
1130 |         verbose: bool = False,
1131 |         json: bool = False,
     |         ^^^^ FBT001
1132 |         plain: bool = False,
1133 |         **kwargs: Any,
     |

src/twat_search/web/cli.py:1131:9: FBT002 Boolean default positional argument in function definition
     |
1129 |         api_key: str | None = None,
1130 |         verbose: bool = False,
1131 |         json: bool = False,
     |         ^^^^ FBT002
1132 |         plain: bool = False,
1133 |         **kwargs: Any,
     |

src/twat_search/web/cli.py:1132:9: FBT001 Boolean-typed positional argument in function definition
     |
1130 |         verbose: bool = False,
1131 |         json: bool = False,
1132 |         plain: bool = False,
     |         ^^^^^ FBT001
1133 |         **kwargs: Any,
1134 |     ) -> list[dict[str, Any]]:
     |

src/twat_search/web/cli.py:1132:9: FBT002 Boolean default positional argument in function definition
     |
1130 |         verbose: bool = False,
1131 |         json: bool = False,
1132 |         plain: bool = False,
     |         ^^^^^ FBT002
1133 |         **kwargs: Any,
1134 |     ) -> list[dict[str, Any]]:
     |

src/twat_search/web/cli.py:1166:15: PLR0913 Too many arguments in function definition (7 > 5)
     |
1164 |         )
1165 |
1166 |     async def hasdata_google_light(
     |               ^^^^^^^^^^^^^^^^^^^^ PLR0913
1167 |         self,
1168 |         query: str,
     |

src/twat_search/web/cli.py:1172:9: FBT001 Boolean-typed positional argument in function definition
     |
1170 |         location: str | None = None,
1171 |         api_key: str | None = None,
1172 |         verbose: bool = False,
     |         ^^^^^^^ FBT001
1173 |         json: bool = False,
1174 |         plain: bool = False,
     |

src/twat_search/web/cli.py:1172:9: FBT002 Boolean default positional argument in function definition
     |
1170 |         location: str | None = None,
1171 |         api_key: str | None = None,
1172 |         verbose: bool = False,
     |         ^^^^^^^ FBT002
1173 |         json: bool = False,
1174 |         plain: bool = False,
     |

src/twat_search/web/cli.py:1173:9: FBT001 Boolean-typed positional argument in function definition
     |
1171 |         api_key: str | None = None,
1172 |         verbose: bool = False,
1173 |         json: bool = False,
     |         ^^^^ FBT001
1174 |         plain: bool = False,
1175 |         **kwargs: Any,
     |

src/twat_search/web/cli.py:1173:9: FBT002 Boolean default positional argument in function definition
     |
1171 |         api_key: str | None = None,
1172 |         verbose: bool = False,
1173 |         json: bool = False,
     |         ^^^^ FBT002
1174 |         plain: bool = False,
1175 |         **kwargs: Any,
     |

src/twat_search/web/cli.py:1174:9: FBT001 Boolean-typed positional argument in function definition
     |
1172 |         verbose: bool = False,
1173 |         json: bool = False,
1174 |         plain: bool = False,
     |         ^^^^^ FBT001
1175 |         **kwargs: Any,
1176 |     ) -> list[dict[str, Any]]:
     |

src/twat_search/web/cli.py:1174:9: FBT002 Boolean default positional argument in function definition
     |
1172 |         verbose: bool = False,
1173 |         json: bool = False,
1174 |         plain: bool = False,
     |         ^^^^^ FBT002
1175 |         **kwargs: Any,
1176 |     ) -> list[dict[str, Any]]:
     |

src/twat_search/web/config.py:51:9: C901 `_load_engine_configs` is too complex (15 > 10)
   |
49 |             self._load_engine_configs()
50 |
51 |     def _load_engine_configs(self) -> None:
   |         ^^^^^^^^^^^^^^^^^^^^ C901
52 |         """
53 |         Load engine configurations from environment variables.
   |

src/twat_search/web/config.py:51:9: PLR0912 Too many branches (15 > 12)
   |
49 |             self._load_engine_configs()
50 |
51 |     def _load_engine_configs(self) -> None:
   |         ^^^^^^^^^^^^^^^^^^^^ PLR0912
52 |         """
53 |         Load engine configurations from environment variables.
   |

src/twat_search/web/engines/__init__.py:14:1: E402 Module level import not at top of file
   |
12 | logger = logging.getLogger(__name__)
13 |
14 | from typing import Any
   | ^^^^^^^^^^^^^^^^^^^^^^ E402
15 | from collections.abc import Callable, Coroutine
   |

src/twat_search/web/engines/__init__.py:15:1: E402 Module level import not at top of file
   |
14 | from typing import Any
15 | from collections.abc import Callable, Coroutine
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ E402
16 |
17 | from twat_search.web.models import SearchResult
   |

src/twat_search/web/engines/__init__.py:17:1: E402 Module level import not at top of file
   |
15 | from collections.abc import Callable, Coroutine
16 |
17 | from twat_search.web.models import SearchResult
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ E402
18 |
19 | # Initialize empty __all__ list
   |

src/twat_search/web/engines/__init__.py:27:23: F401 `.base.register_engine` imported but unused; consider using `importlib.util.find_spec` to test for availability
   |
25 | # Import base functionality first
26 | try:
27 |     from .base import register_engine, get_engine, get_registered_engines, SearchEngine
   |                       ^^^^^^^^^^^^^^^ F401
28 |
29 |     __all__.extend(
   |
   = help: Remove unused import

src/twat_search/web/engines/__init__.py:27:40: F401 `.base.get_engine` imported but unused; consider using `importlib.util.find_spec` to test for availability
   |
25 | # Import base functionality first
26 | try:
27 |     from .base import register_engine, get_engine, get_registered_engines, SearchEngine
   |                                        ^^^^^^^^^^ F401
28 |
29 |     __all__.extend(
   |
   = help: Remove unused import

src/twat_search/web/engines/__init__.py:27:52: F401 `.base.get_registered_engines` imported but unused; consider using `importlib.util.find_spec` to test for availability
   |
25 | # Import base functionality first
26 | try:
27 |     from .base import register_engine, get_engine, get_registered_engines, SearchEngine
   |                                                    ^^^^^^^^^^^^^^^^^^^^^^ F401
28 |
29 |     __all__.extend(
   |
   = help: Remove unused import

src/twat_search/web/engines/__init__.py:27:76: F401 `.base.SearchEngine` imported but unused; consider using `importlib.util.find_spec` to test for availability
   |
25 | # Import base functionality first
26 | try:
27 |     from .base import register_engine, get_engine, get_registered_engines, SearchEngine
   |                                                                            ^^^^^^^^^^^^ F401
28 |
29 |     __all__.extend(
   |
   = help: Remove unused import

src/twat_search/web/engines/__init__.py:37:24: F401 `.brave.BraveSearchEngine` imported but unused; consider using `importlib.util.find_spec` to test for availability
   |
35 | # Import each engine module and add its components to __all__ if successful
36 | try:
37 |     from .brave import BraveSearchEngine, BraveNewsSearchEngine, brave, brave_news
   |                        ^^^^^^^^^^^^^^^^^ F401
38 |
39 |     available_engine_functions["brave"] = brave
   |
   = help: Remove unused import

src/twat_search/web/engines/__init__.py:37:43: F401 `.brave.BraveNewsSearchEngine` imported but unused; consider using `importlib.util.find_spec` to test for availability
   |
35 | # Import each engine module and add its components to __all__ if successful
36 | try:
37 |     from .brave import BraveSearchEngine, BraveNewsSearchEngine, brave, brave_news
   |                                           ^^^^^^^^^^^^^^^^^^^^^ F401
38 |
39 |     available_engine_functions["brave"] = brave
   |
   = help: Remove unused import

src/twat_search/web/engines/__init__.py:48:26: F401 `.serpapi.SerpApiSearchEngine` imported but unused; consider using `importlib.util.find_spec` to test for availability
   |
47 | try:
48 |     from .serpapi import SerpApiSearchEngine, serpapi
   |                          ^^^^^^^^^^^^^^^^^^^ F401
49 |
50 |     available_engine_functions["serpapi"] = serpapi
   |
   = help: Remove unused import: `.serpapi.SerpApiSearchEngine`

src/twat_search/web/engines/__init__.py:56:25: F401 `.tavily.TavilySearchEngine` imported but unused; consider using `importlib.util.find_spec` to test for availability
   |
55 | try:
56 |     from .tavily import TavilySearchEngine, tavily
   |                         ^^^^^^^^^^^^^^^^^^ F401
57 |
58 |     available_engine_functions["tavily"] = tavily
   |
   = help: Remove unused import: `.tavily.TavilySearchEngine`

src/twat_search/web/engines/__init__.py:64:23: F401 `.pplx.PerplexitySearchEngine` imported but unused; consider using `importlib.util.find_spec` to test for availability
   |
63 | try:
64 |     from .pplx import PerplexitySearchEngine, pplx
   |                       ^^^^^^^^^^^^^^^^^^^^^^ F401
65 |
66 |     available_engine_functions["pplx"] = pplx
   |
   = help: Remove unused import: `.pplx.PerplexitySearchEngine`

src/twat_search/web/engines/__init__.py:72:22: F401 `.you.YouSearchEngine` imported but unused; consider using `importlib.util.find_spec` to test for availability
   |
71 | try:
72 |     from .you import YouSearchEngine, YouNewsSearchEngine, you, you_news
   |                      ^^^^^^^^^^^^^^^ F401
73 |
74 |     available_engine_functions["you"] = you
   |
   = help: Remove unused import

src/twat_search/web/engines/__init__.py:72:39: F401 `.you.YouNewsSearchEngine` imported but unused; consider using `importlib.util.find_spec` to test for availability
   |
71 | try:
72 |     from .you import YouSearchEngine, YouNewsSearchEngine, you, you_news
   |                                       ^^^^^^^^^^^^^^^^^^^ F401
73 |
74 |     available_engine_functions["you"] = you
   |
   = help: Remove unused import

src/twat_search/web/engines/__init__.py:81:27: F401 `.critique.CritiqueSearchEngine` imported but unused; consider using `importlib.util.find_spec` to test for availability
   |
80 | try:
81 |     from .critique import CritiqueSearchEngine, critique
   |                           ^^^^^^^^^^^^^^^^^^^^ F401
82 |
83 |     available_engine_functions["critique"] = critique
   |
   = help: Remove unused import: `.critique.CritiqueSearchEngine`

src/twat_search/web/engines/__init__.py:89:29: F401 `.duckduckgo.DuckDuckGoSearchEngine` imported but unused; consider using `importlib.util.find_spec` to test for availability
   |
88 | try:
89 |     from .duckduckgo import DuckDuckGoSearchEngine, duckduckgo
   |                             ^^^^^^^^^^^^^^^^^^^^^^ F401
90 |
91 |     available_engine_functions["duckduckgo"] = duckduckgo
   |
   = help: Remove unused import: `.duckduckgo.DuckDuckGoSearchEngine`

src/twat_search/web/engines/__init__.py:97:31: F401 `.bing_scraper.BingScraperSearchEngine` imported but unused; consider using `importlib.util.find_spec` to test for availability
   |
96 | try:
97 |     from .bing_scraper import BingScraperSearchEngine, bing_scraper
   |                               ^^^^^^^^^^^^^^^^^^^^^^^ F401
98 |
99 |     available_engine_functions["bing_scraper"] = bing_scraper
   |
   = help: Remove unused import: `.bing_scraper.BingScraperSearchEngine`

src/twat_search/web/engines/__init__.py:107:9: F401 `.hasdata.HasDataGoogleEngine` imported but unused; consider using `importlib.util.find_spec` to test for availability
    |
105 | try:
106 |     from .hasdata import (
107 |         HasDataGoogleEngine,
    |         ^^^^^^^^^^^^^^^^^^^ F401
108 |         HasDataGoogleLightEngine,
109 |         hasdata_google,
    |
    = help: Remove unused import

src/twat_search/web/engines/__init__.py:108:9: F401 `.hasdata.HasDataGoogleLightEngine` imported but unused; consider using `importlib.util.find_spec` to test for availability
    |
106 |     from .hasdata import (
107 |         HasDataGoogleEngine,
108 |         HasDataGoogleLightEngine,
    |         ^^^^^^^^^^^^^^^^^^^^^^^^ F401
109 |         hasdata_google,
110 |         hasdata_google_light,
    |
    = help: Remove unused import

src/twat_search/web/engines/anywebsearch.py:51:13: FBT001 Boolean-typed positional argument in function definition
   |
49 |             language: str = "en",
50 |             num_results: int = 20,
51 |             merge: bool = True,
   |             ^^^^^ FBT001
52 |             engines: list[str] | None = None,
53 |             **kwargs: Any,
   |

src/twat_search/web/engines/anywebsearch.py:51:13: FBT002 Boolean default positional argument in function definition
   |
49 |             language: str = "en",
50 |             num_results: int = 20,
51 |             merge: bool = True,
   |             ^^^^^ FBT002
52 |             engines: list[str] | None = None,
53 |             **kwargs: Any,
   |

src/twat_search/web/engines/anywebsearch.py:126:9: PLR0913 Too many arguments in function definition (6 > 5)
    |
124 |     env_api_key_names: ClassVar[list[str]] = []  # Override as needed
125 |
126 |     def __init__(
    |         ^^^^^^^^ PLR0913
127 |         self,
128 |         config: EngineConfig,
    |

src/twat_search/web/engines/anywebsearch.py:132:9: FBT002 Boolean default positional argument in function definition
    |
130 |         country: str | None = None,
131 |         language: str | None = None,
132 |         safe_search: bool | str | None = True,
    |         ^^^^^^^^^^^ FBT002
133 |         time_frame: str | None = None,
134 |         **kwargs: Any,
    |

src/twat_search/web/engines/bing_scraper.py:43:26: ARG002 Unused method argument: `query`
   |
41 |             self.delay_between_requests = delay_between_requests
42 |
43 |         def search(self, query: str, num_results: int = 10) -> list[Any]:
   |                          ^^^^^ ARG002
44 |             """
45 |             Dummy search method for type checking.
   |

src/twat_search/web/engines/bing_scraper.py:43:38: ARG002 Unused method argument: `num_results`
   |
41 |             self.delay_between_requests = delay_between_requests
42 |
43 |         def search(self, query: str, num_results: int = 10) -> list[Any]:
   |                                      ^^^^^^^^^^^ ARG002
44 |             """
45 |             Dummy search method for type checking.
   |

src/twat_search/web/engines/bing_scraper.py:101:9: PLR0913 Too many arguments in function definition (6 > 5)
    |
 99 |     env_api_key_names: ClassVar[list[str]] = []  # No API key needed
100 |
101 |     def __init__(
    |         ^^^^^^^^ PLR0913
102 |         self,
103 |         config: EngineConfig,
    |

src/twat_search/web/engines/bing_scraper.py:107:9: FBT002 Boolean default positional argument in function definition
    |
105 |         country: str | None = None,
106 |         language: str | None = None,
107 |         safe_search: bool | str | None = True,
    |         ^^^^^^^^^^^ FBT002
108 |         time_frame: str | None = None,
109 |         **kwargs: Any,
    |

src/twat_search/web/engines/brave.py:33:9: PLR0913 Too many arguments in function definition (6 > 5)
   |
31 |     result_model: type[BaseModel]  # Either BraveResult or BraveNewsResult.
32 |
33 |     def __init__(
   |         ^^^^^^^^ PLR0913
34 |         self,
35 |         config: EngineConfig,
   |

src/twat_search/web/engines/brave.py:39:9: FBT002 Boolean default positional argument in function definition
   |
37 |         country: str | None = None,
38 |         language: str | None = None,
39 |         safe_search: bool | str | None = True,
   |         ^^^^^^^^^^^ FBT002
40 |         time_frame: str | None = None,
41 |         **kwargs: Any,
   |

src/twat_search/web/engines/brave.py:74:15: C901 `search` is too complex (12 > 10)
   |
72 |         }
73 |
74 |     async def search(self, query: str) -> list[SearchResult]:
   |               ^^^^^^ C901
75 |         params: dict[str, Any] = {"q": query, "count": self.count}
76 |         if self.country:
   |

src/twat_search/web/engines/brave.py:150:11: PLR0913 Too many arguments in function definition (7 > 5)
    |
150 | async def brave(
    |           ^^^^^ PLR0913
151 |     query: str,
152 |     num_results: int = 5,
    |

src/twat_search/web/engines/brave.py:155:5: FBT002 Boolean default positional argument in function definition
    |
153 |     country: str | None = None,
154 |     language: str | None = None,
155 |     safe_search: bool | str | None = True,
    |     ^^^^^^^^^^^ FBT002
156 |     time_frame: str | None = None,
157 |     api_key: str | None = None,
    |

src/twat_search/web/engines/brave.py:186:11: PLR0913 Too many arguments in function definition (7 > 5)
    |
186 | async def brave_news(
    |           ^^^^^^^^^^ PLR0913
187 |     query: str,
188 |     num_results: int = 5,
    |

src/twat_search/web/engines/brave.py:191:5: FBT002 Boolean default positional argument in function definition
    |
189 |     country: str | None = None,
190 |     language: str | None = None,
191 |     safe_search: bool | str | None = True,
    |     ^^^^^^^^^^^ FBT002
192 |     time_frame: str | None = None,
193 |     api_key: str | None = None,
    |

src/twat_search/web/engines/critique.py:50:9: PLR0913 Too many arguments in function definition (11 > 5)
   |
48 |     ]
49 |
50 |     def __init__(
   |         ^^^^^^^^ PLR0913
51 |         self,
52 |         config: EngineConfig,
   |

src/twat_search/web/engines/critique.py:56:9: FBT002 Boolean default positional argument in function definition
   |
54 |         country: str | None = None,
55 |         language: str | None = None,
56 |         safe_search: bool | None = True,
   |         ^^^^^^^^^^^ FBT002
57 |         time_frame: str | None = None,
58 |         image_url: str | None = None,
   |

src/twat_search/web/engines/critique.py:105:13: B904 Within an `except` clause, raise exceptions with `raise ... from err` or `raise ... from None` to distinguish them from errors in exception handling
    |
103 |                 return f"data:image/jpeg;base64,{encoded}"
104 |         except httpx.RequestError as e:
105 |             raise EngineError(self.name, f"Failed to fetch image from URL: {e}")
    |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ B904
106 |         except Exception as e:
107 |             raise EngineError(self.name, f"Error processing image: {e}")
    |

src/twat_search/web/engines/critique.py:107:13: B904 Within an `except` clause, raise exceptions with `raise ... from err` or `raise ... from None` to distinguish them from errors in exception handling
    |
105 |             raise EngineError(self.name, f"Failed to fetch image from URL: {e}")
106 |         except Exception as e:
107 |             raise EngineError(self.name, f"Error processing image: {e}")
    |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ B904
108 |
109 |     async def _build_payload(self, query: str) -> dict[str, Any]:
    |

src/twat_search/web/engines/critique.py:211:11: PLR0913 Too many arguments in function definition (12 > 5)
    |
211 | async def critique(
    |           ^^^^^^^^ PLR0913
212 |     query: str,
213 |     num_results: int = 5,
    |

src/twat_search/web/engines/critique.py:216:5: FBT002 Boolean default positional argument in function definition
    |
214 |     country: str | None = None,
215 |     language: str | None = None,
216 |     safe_search: bool | None = True,
    |     ^^^^^^^^^^^ FBT002
217 |     time_frame: str | None = None,
218 |     image_url: str | None = None,
    |

src/twat_search/web/engines/duckduckgo.py:44:9: PLR0913 Too many arguments in function definition (6 > 5)
   |
42 |     env_api_key_names: ClassVar[list[str]] = []  # No API key needed
43 |
44 |     def __init__(
   |         ^^^^^^^^ PLR0913
45 |         self,
46 |         config: EngineConfig,
   |

src/twat_search/web/engines/duckduckgo.py:50:9: FBT002 Boolean default positional argument in function definition
   |
48 |         country: str | None = None,
49 |         language: str | None = None,
50 |         safe_search: bool | str | None = True,
   |         ^^^^^^^^^^^ FBT002
51 |         time_frame: str | None = None,
52 |         **kwargs: Any,
   |

src/twat_search/web/engines/duckduckgo.py:84:9: PLR0913 Too many arguments in function definition (7 > 5)
   |
83 |     @staticmethod
84 |     def _map_init_params(
   |         ^^^^^^^^^^^^^^^^ PLR0913
85 |         num_results: int,
86 |         country: str | None,
   |

src/twat_search/web/engines/duckduckgo.py:172:11: PLR0913 Too many arguments in function definition (8 > 5)
    |
172 | async def duckduckgo(
    |           ^^^^^^^^^^ PLR0913
173 |     query: str,
174 |     num_results: int = 5,
    |

src/twat_search/web/engines/duckduckgo.py:177:5: FBT001 Boolean-typed positional argument in function definition
    |
175 |     country: str | None = None,
176 |     language: str | None = None,
177 |     safe_search: bool = True,
    |     ^^^^^^^^^^^ FBT001
178 |     time_frame: str | None = None,
179 |     proxy: str | None = None,
    |

src/twat_search/web/engines/duckduckgo.py:177:5: FBT002 Boolean default positional argument in function definition
    |
175 |     country: str | None = None,
176 |     language: str | None = None,
177 |     safe_search: bool = True,
    |     ^^^^^^^^^^^ FBT002
178 |     time_frame: str | None = None,
179 |     proxy: str | None = None,
    |

src/twat_search/web/engines/google_scraper.py:99:9: PLR0913 Too many arguments in function definition (6 > 5)
    |
 97 |     env_api_key_names: ClassVar[list[str]] = []  # No API key needed
 98 |
 99 |     def __init__(
    |         ^^^^^^^^ PLR0913
100 |         self,
101 |         config: EngineConfig,
    |

src/twat_search/web/engines/google_scraper.py:105:9: FBT002 Boolean default positional argument in function definition
    |
103 |         country: str | None = None,
104 |         language: str | None = None,
105 |         safe_search: bool | str | None = True,
    |         ^^^^^^^^^^^ FBT002
106 |         time_frame: str | None = None,
107 |         **kwargs: Any,
    |

src/twat_search/web/engines/google_scraper.py:292:11: PLR0913 Too many arguments in function definition (9 > 5)
    |
292 | async def google_scraper(
    |           ^^^^^^^^^^^^^^ PLR0913
293 |     query: str,
294 |     num_results: int = 5,
    |

src/twat_search/web/engines/google_scraper.py:297:5: FBT001 Boolean-typed positional argument in function definition
    |
295 |     language: str = "en",
296 |     country: str | None = None,
297 |     safe_search: bool = True,
    |     ^^^^^^^^^^^ FBT001
298 |     sleep_interval: float = 0.0,
299 |     ssl_verify: bool | None = None,
    |

src/twat_search/web/engines/google_scraper.py:297:5: FBT002 Boolean default positional argument in function definition
    |
295 |     language: str = "en",
296 |     country: str | None = None,
297 |     safe_search: bool = True,
    |     ^^^^^^^^^^^ FBT002
298 |     sleep_interval: float = 0.0,
299 |     ssl_verify: bool | None = None,
    |

src/twat_search/web/engines/google_scraper.py:301:5: FBT001 Boolean-typed positional argument in function definition
    |
299 |     ssl_verify: bool | None = None,
300 |     proxy: str | None = None,
301 |     unique: bool = True,
    |     ^^^^^^ FBT001
302 |     **kwargs: Any,
303 | ) -> list[SearchResult]:
    |

src/twat_search/web/engines/google_scraper.py:301:5: FBT002 Boolean default positional argument in function definition
    |
299 |     ssl_verify: bool | None = None,
300 |     proxy: str | None = None,
301 |     unique: bool = True,
    |     ^^^^^^ FBT002
302 |     **kwargs: Any,
303 | ) -> list[SearchResult]:
    |

src/twat_search/web/engines/pplx.py:40:9: PLR0913 Too many arguments in function definition (7 > 5)
   |
38 |     ]
39 |
40 |     def __init__(
   |         ^^^^^^^^ PLR0913
41 |         self,
42 |         config: EngineConfig,
   |

src/twat_search/web/engines/pplx.py:46:9: FBT002 Boolean default positional argument in function definition
   |
44 |         country: str | None = None,
45 |         language: str | None = None,
46 |         safe_search: bool | None = True,
   |         ^^^^^^^^^^^ FBT002
47 |         time_frame: str | None = None,
48 |         model: str | None = None,
   |

src/twat_search/web/engines/pplx.py:133:11: PLR0913 Too many arguments in function definition (8 > 5)
    |
133 | async def pplx(
    |           ^^^^ PLR0913
134 |     query: str,
135 |     num_results: int = 5,
    |

src/twat_search/web/engines/pplx.py:138:5: FBT002 Boolean default positional argument in function definition
    |
136 |     country: str | None = None,
137 |     language: str | None = None,
138 |     safe_search: bool | None = True,
    |     ^^^^^^^^^^^ FBT002
139 |     time_frame: str | None = None,
140 |     model: str | None = None,
    |

src/twat_search/web/engines/searchit.py:55:13: PLR0913 Too many arguments in function definition (7 > 5)
   |
53 |         """Dummy class for type checking when searchit is not installed."""
54 |
55 |         def __init__(
   |             ^^^^^^^^ PLR0913
56 |             self,
57 |             term: str,
   |

src/twat_search/web/engines/searchit.py:97:32: ARG002 Unused method argument: `request`
   |
95 |             self.max_results = max_results_per_page
96 |
97 |         async def scrape(self, request: ScrapeRequest) -> list[SearchitResult]:
   |                                ^^^^^^^ ARG002
98 |             """
99 |             Dummy scrape method for type checking.
   |

src/twat_search/web/engines/searchit.py:121:32: ARG002 Unused method argument: `request`
    |
119 |             self.max_results = max_results_per_page
120 |
121 |         async def scrape(self, request: ScrapeRequest) -> list[SearchitResult]:
    |                                ^^^^^^^ ARG002
122 |             """
123 |             Dummy scrape method for type checking.
    |

src/twat_search/web/engines/searchit.py:145:32: ARG002 Unused method argument: `request`
    |
143 |             self.max_results = max_results_per_page
144 |
145 |         async def scrape(self, request: ScrapeRequest) -> list[SearchitResult]:
    |                                ^^^^^^^ ARG002
146 |             """
147 |             Dummy scrape method for type checking.
    |

src/twat_search/web/engines/searchit.py:169:32: ARG002 Unused method argument: `request`
    |
167 |             self.max_results = max_results_per_page
168 |
169 |         async def scrape(self, request: ScrapeRequest) -> list[SearchitResult]:
    |                                ^^^^^^^ ARG002
170 |             """
171 |             Dummy scrape method for type checking.
    |

src/twat_search/web/engines/searchit.py:232:9: PLR0913 Too many arguments in function definition (6 > 5)
    |
230 |     env_api_key_names: ClassVar[list[str]] = []  # No API key needed for scrapers
231 |
232 |     def __init__(
    |         ^^^^^^^^ PLR0913
233 |         self,
234 |         config: EngineConfig,
    |

src/twat_search/web/engines/searchit.py:238:9: FBT002 Boolean default positional argument in function definition
    |
236 |         country: str | None = None,
237 |         language: str | None = None,
238 |         safe_search: bool | str | None = True,
    |         ^^^^^^^^^^^ FBT002
239 |         time_frame: str | None = None,
240 |         **kwargs: Any,
    |

src/twat_search/web/engines/searchit.py:675:11: PLR0913 Too many arguments in function definition (6 > 5)
    |
674 | # Convenience functions for each engine
675 | async def google_searchit(
    |           ^^^^^^^^^^^^^^^ PLR0913
676 |     query: str,
677 |     num_results: int = 5,
    |

src/twat_search/web/engines/searchit.py:716:11: PLR0913 Too many arguments in function definition (6 > 5)
    |
716 | async def yandex_searchit(
    |           ^^^^^^^^^^^^^^^ PLR0913
717 |     query: str,
718 |     num_results: int = 5,
    |

src/twat_search/web/engines/searchit.py:757:11: PLR0913 Too many arguments in function definition (6 > 5)
    |
757 | async def qwant_searchit(
    |           ^^^^^^^^^^^^^^ PLR0913
758 |     query: str,
759 |     num_results: int = 5,
    |

src/twat_search/web/engines/searchit.py:798:11: PLR0913 Too many arguments in function definition (6 > 5)
    |
798 | async def bing_searchit(
    |           ^^^^^^^^^^^^^ PLR0913
799 |     query: str,
800 |     num_results: int = 5,
    |

src/twat_search/web/engines/serpapi.py:49:9: PLR0913 Too many arguments in function definition (6 > 5)
   |
47 |     env_api_key_names: ClassVar[list[str]] = ["SERPAPI_API_KEY"]
48 |
49 |     def __init__(
   |         ^^^^^^^^ PLR0913
50 |         self,
51 |         config: EngineConfig,
   |

src/twat_search/web/engines/serpapi.py:55:9: FBT002 Boolean default positional argument in function definition
   |
53 |         country: str | None = None,
54 |         language: str | None = None,
55 |         safe_search: bool | str | None = True,
   |         ^^^^^^^^^^^ FBT002
56 |         time_frame: str | None = None,
57 |         **kwargs: Any,
   |

src/twat_search/web/engines/serpapi.py:166:11: PLR0913 Too many arguments in function definition (8 > 5)
    |
166 | async def serpapi(
    |           ^^^^^^^ PLR0913
167 |     query: str,
168 |     num_results: int = 5,
    |

src/twat_search/web/engines/serpapi.py:171:5: FBT002 Boolean default positional argument in function definition
    |
169 |     country: str | None = None,
170 |     language: str | None = None,
171 |     safe_search: bool | str | None = True,
    |     ^^^^^^^^^^^ FBT002
172 |     time_frame: str | None = None,
173 |     google_domain: str | None = None,
    |

src/twat_search/web/engines/tavily.py:53:9: PLR0913 Too many arguments in function definition (12 > 5)
   |
51 |     env_api_key_names: ClassVar[list[str]] = ["TAVILY_API_KEY"]
52 |
53 |     def __init__(
   |         ^^^^^^^^ PLR0913
54 |         self,
55 |         config: EngineConfig,
   |

src/twat_search/web/engines/tavily.py:59:9: FBT002 Boolean default positional argument in function definition
   |
57 |         country: str | None = None,
58 |         language: str | None = None,
59 |         safe_search: bool | None = True,
   |         ^^^^^^^^^^^ FBT002
60 |         time_frame: str | None = None,
61 |         search_depth: str = "basic",
   |

src/twat_search/web/engines/tavily.py:64:9: FBT001 Boolean-typed positional argument in function definition
   |
62 |         include_domains: list[str] | None = None,
63 |         exclude_domains: list[str] | None = None,
64 |         include_answer: bool = False,
   |         ^^^^^^^^^^^^^^ FBT001
65 |         max_tokens: int | None = None,
66 |         search_type: str = "search",
   |

src/twat_search/web/engines/tavily.py:64:9: FBT002 Boolean default positional argument in function definition
   |
62 |         include_domains: list[str] | None = None,
63 |         exclude_domains: list[str] | None = None,
64 |         include_answer: bool = False,
   |         ^^^^^^^^^^^^^^ FBT002
65 |         max_tokens: int | None = None,
66 |         search_type: str = "search",
   |

src/twat_search/web/engines/tavily.py:92:77: FBT003 Boolean positional value in function call
   |
90 |         self.include_domains = get_default(include_domains, "include_domains", None)
91 |         self.exclude_domains = get_default(exclude_domains, "exclude_domains", None)
92 |         self.include_answer = get_default(include_answer, "include_answer", False)
   |                                                                             ^^^^^ FBT003
93 |         self.max_tokens = get_default(max_tokens, "max_tokens", None)
94 |         self.search_type = get_default(search_type, "search_type", "search")
   |

src/twat_search/web/engines/tavily.py:171:17: B904 Within an `except` clause, raise exceptions with `raise ... from err` or `raise ... from None` to distinguish them from errors in exception handling
    |
169 |                 data = response.json()
170 |             except httpx.HTTPStatusError as e:
171 |                 raise EngineError(self.name, f"HTTP error: {e}")
    |                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ B904
172 |             except httpx.RequestError as e:
173 |                 raise EngineError(self.name, f"Request error: {e}")
    |

src/twat_search/web/engines/tavily.py:173:17: B904 Within an `except` clause, raise exceptions with `raise ... from err` or `raise ... from None` to distinguish them from errors in exception handling
    |
171 |                 raise EngineError(self.name, f"HTTP error: {e}")
172 |             except httpx.RequestError as e:
173 |                 raise EngineError(self.name, f"Request error: {e}")
    |                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ B904
174 |             except Exception as e:
175 |                 raise EngineError(self.name, f"Error: {e!s}")
    |

src/twat_search/web/engines/tavily.py:175:17: B904 Within an `except` clause, raise exceptions with `raise ... from err` or `raise ... from None` to distinguish them from errors in exception handling
    |
173 |                 raise EngineError(self.name, f"Request error: {e}")
174 |             except Exception as e:
175 |                 raise EngineError(self.name, f"Error: {e!s}")
    |                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ B904
176 |
177 |         results = []
    |

src/twat_search/web/engines/tavily.py:193:11: PLR0913 Too many arguments in function definition (13 > 5)
    |
193 | async def tavily(
    |           ^^^^^^ PLR0913
194 |     query: str,
195 |     num_results: int = 5,
    |

src/twat_search/web/engines/tavily.py:198:5: FBT002 Boolean default positional argument in function definition
    |
196 |     country: str | None = None,
197 |     language: str | None = None,
198 |     safe_search: bool | None = True,
    |     ^^^^^^^^^^^ FBT002
199 |     time_frame: str | None = None,
200 |     search_depth: str = "basic",
    |

src/twat_search/web/engines/tavily.py:203:5: FBT001 Boolean-typed positional argument in function definition
    |
201 |     include_domains: list[str] | None = None,
202 |     exclude_domains: list[str] | None = None,
203 |     include_answer: bool = False,
    |     ^^^^^^^^^^^^^^ FBT001
204 |     max_tokens: int | None = None,
205 |     search_type: str = "search",
    |

src/twat_search/web/engines/tavily.py:203:5: FBT002 Boolean default positional argument in function definition
    |
201 |     include_domains: list[str] | None = None,
202 |     exclude_domains: list[str] | None = None,
203 |     include_answer: bool = False,
    |     ^^^^^^^^^^^^^^ FBT002
204 |     max_tokens: int | None = None,
205 |     search_type: str = "search",
    |

src/twat_search/web/engines/tavily.py:211:18: RUF002 Docstring contains ambiguous `’` (RIGHT SINGLE QUOTATION MARK). Did you mean ``` (GRAVE ACCENT)?
    |
209 |     Search using Tavily AI search.
210 |
211 |     This function’s API and signature remain identical.
    |                  ^ RUF002
212 |     """
213 |     config = EngineConfig(
    |

src/twat_search/web/engines/you.py:40:9: PLR0913 Too many arguments in function definition (6 > 5)
   |
38 |     env_api_key_names: ClassVar[list[str]] = ["YOU_API_KEY"]
39 |
40 |     def __init__(
   |         ^^^^^^^^ PLR0913
41 |         self,
42 |         config: EngineConfig,
   |

src/twat_search/web/engines/you.py:45:9: ARG002 Unused method argument: `language`
   |
43 |         num_results: int = 5,
44 |         country: str | None = None,
45 |         language: str | None = None,
   |         ^^^^^^^^ ARG002
46 |         safe_search: bool | None = True,
47 |         time_frame: str | None = None,
   |

src/twat_search/web/engines/you.py:46:9: FBT002 Boolean default positional argument in function definition
   |
44 |         country: str | None = None,
45 |         language: str | None = None,
46 |         safe_search: bool | None = True,
   |         ^^^^^^^^^^^ FBT002
47 |         time_frame: str | None = None,
48 |         **kwargs: Any,
   |

src/twat_search/web/engines/you.py:47:9: ARG002 Unused method argument: `time_frame`
   |
45 |         language: str | None = None,
46 |         safe_search: bool | None = True,
47 |         time_frame: str | None = None,
   |         ^^^^^^^^^^ ARG002
48 |         **kwargs: Any,
49 |     ) -> None:
   |

src/twat_search/web/engines/you.py:48:11: ARG002 Unused method argument: `kwargs`
   |
46 |         safe_search: bool | None = True,
47 |         time_frame: str | None = None,
48 |         **kwargs: Any,
   |           ^^^^^^ ARG002
49 |     ) -> None:
50 |         """Initialize the base engine with common parameters."""
   |

src/twat_search/web/engines/you.py:186:11: PLR0913 Too many arguments in function definition (7 > 5)
    |
186 | async def you(
    |           ^^^ PLR0913
187 |     query: str,
188 |     num_results: int = 5,
    |

src/twat_search/web/engines/you.py:191:5: FBT002 Boolean default positional argument in function definition
    |
189 |     country: str | None = None,
190 |     language: str | None = None,
191 |     safe_search: bool | None = True,
    |     ^^^^^^^^^^^ FBT002
192 |     time_frame: str | None = None,
193 |     api_key: str | None = None,
    |

src/twat_search/web/engines/you.py:208:11: PLR0913 Too many arguments in function definition (7 > 5)
    |
208 | async def you_news(
    |           ^^^^^^^^ PLR0913
209 |     query: str,
210 |     num_results: int = 5,
    |

src/twat_search/web/engines/you.py:213:5: FBT002 Boolean default positional argument in function definition
    |
211 |     country: str | None = None,
212 |     language: str | None = None,
213 |     safe_search: bool | None = True,
    |     ^^^^^^^^^^^ FBT002
214 |     time_frame: str | None = None,
215 |     api_key: str | None = None,
    |

src/twat_search/web/utils.py:48:33: PLR2004 Magic value used in comparison, consider replacing `0.1` with a constant variable
   |
46 |             sleep_time = 1 - (now - self.call_timestamps[0])
47 |             if sleep_time > 0:
48 |                 if sleep_time > 0.1:  # Only log if sleep time is significant
   |                                 ^^^ PLR2004
49 |                     logger.debug(f"Rate limiting: sleeping for {sleep_time:.2f}s")
50 |                 time.sleep(sleep_time)
   |

tests/unit/web/engines/test_base.py:88:32: ARG002 Unused method argument: `query`
   |
86 |         name = "new_engine"
87 |
88 |         async def search(self, query: str) -> list[SearchResult]:
   |                                ^^^^^ ARG002
89 |             return []
   |

tests/unit/web/engines/test_base.py:137:40: PLR2004 Magic value used in comparison, consider replacing `30` with a constant variable
    |
135 |     # Check that kwargs were stored by the engine
136 |     assert engine.kwargs == kwargs
137 |     assert engine.kwargs["timeout"] == 30
    |                                        ^^ PLR2004
138 |     assert engine.kwargs["country"] == "US"
    |

tests/unit/web/test_api.py:104:28: PLR2004 Magic value used in comparison, consider replacing `2` with a constant variable
    |
102 |     results = await search("test query", engines=["mock"], config=mock_config)
103 |
104 |     assert len(results) == 2
    |                            ^ PLR2004
105 |     assert all(isinstance(result, SearchResult) for result in results)
106 |     assert all(result.source == "mock" for result in results)
    |

tests/unit/web/test_api.py:119:28: PLR2004 Magic value used in comparison, consider replacing `3` with a constant variable
    |
117 |     )
118 |
119 |     assert len(results) == 3
    |                            ^ PLR2004
    |

tests/unit/web/test_api.py:131:28: PLR2004 Magic value used in comparison, consider replacing `4` with a constant variable
    |
129 |     )
130 |
131 |     assert len(results) == 4
    |                            ^ PLR2004
    |

tests/unit/web/test_exceptions.py:64:31: PLR2004 Magic value used in comparison, consider replacing `2` with a constant variable
   |
62 |         exceptions.append(e)
63 |
64 |     assert len(exceptions) == 2
   |                               ^ PLR2004
65 |     assert isinstance(exceptions[0], SearchError)
66 |     assert isinstance(exceptions[1], EngineError)
   |

tests/unit/web/test_utils.py:28:40: PLR2004 Magic value used in comparison, consider replacing `10` with a constant variable
   |
26 |     """Test RateLimiter initialization."""
27 |     limiter = RateLimiter(calls_per_second=10)
28 |     assert limiter.calls_per_second == 10
   |                                        ^^ PLR2004
29 |     assert limiter.call_timestamps == []
   |

tests/web/test_bing_scraper.py:67:25: N803 Argument name `mock_BingScraper` should be lowercase
   |
66 |     @patch("twat_search.web.engines.bing_scraper.BingScraper")
67 |     def test_init(self, mock_BingScraper: MagicMock, engine: Any) -> None:
   |                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^ N803
68 |         """Test BingScraperSearchEngine initialization."""
69 |         assert engine.name == "bing-scraper"
   |

tests/web/test_bing_scraper.py:70:38: PLR2004 Magic value used in comparison, consider replacing `5` with a constant variable
   |
68 |         """Test BingScraperSearchEngine initialization."""
69 |         assert engine.name == "bing-scraper"
70 |         assert engine.max_results == 5
   |                                      ^ PLR2004
71 |         assert engine.max_retries == 3
72 |         assert engine.delay_between_requests == 1.0
   |

tests/web/test_bing_scraper.py:71:38: PLR2004 Magic value used in comparison, consider replacing `3` with a constant variable
   |
69 |         assert engine.name == "bing-scraper"
70 |         assert engine.max_results == 5
71 |         assert engine.max_retries == 3
   |                                      ^ PLR2004
72 |         assert engine.delay_between_requests == 1.0
73 |         # Lazy initialization - BingScraper shouldn't be called yet
   |

tests/web/test_bing_scraper.py:80:9: N803 Argument name `mock_BingScraper` should be lowercase
   |
78 |     async def test_search_basic(
79 |         self,
80 |         mock_BingScraper: MagicMock,
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^ N803
81 |         engine: BingScraperSearchEngine,
82 |         mock_results: list[MockSearchResult],
   |

tests/web/test_bing_scraper.py:94:32: PLR2004 Magic value used in comparison, consider replacing `2` with a constant variable
   |
93 |         # Verify results
94 |         assert len(results) == 2
   |                                ^ PLR2004
95 |         assert isinstance(results[0], SearchResult)
96 |         assert results[0].title == "Test Result 1"
   |

tests/web/test_bing_scraper.py:109:44: N803 Argument name `mock_BingScraper` should be lowercase
    |
107 |     @patch("twat_search.web.engines.bing_scraper.BingScraper")
108 |     @pytest.mark.asyncio
109 |     async def test_custom_parameters(self, mock_BingScraper: MagicMock) -> None:
    |                                            ^^^^^^^^^^^^^^^^^^^^^^^^^^^ N803
110 |         """Test custom parameters for engine initialization."""
111 |         # Create engine with custom parameters
    |

tests/web/test_bing_scraper.py:141:15: N803 Argument name `mock_BingScraper` should be lowercase
    |
139 |     @pytest.mark.asyncio
140 |     async def test_invalid_url_handling(
141 |         self, mock_BingScraper: MagicMock, engine: BingScraperSearchEngine
    |               ^^^^^^^^^^^^^^^^^^^^^^^^^^^ N803
142 |     ) -> None:
143 |         """Test handling of invalid URLs."""
    |

tests/web/test_bing_scraper.py:202:46: PLR2004 Magic value used in comparison, consider replacing `10` with a constant variable
    |
200 |         assert call_args[0] == "test query"
201 |         assert call_kwargs["engines"] == ["bing-scraper"]
202 |         assert call_kwargs["num_results"] == 10
    |                                              ^^ PLR2004
203 |         assert call_kwargs["bing_scraper_max_retries"] == 5
204 |         assert call_kwargs["bing_scraper_delay_between_requests"] == 2.0
    |

tests/web/test_bing_scraper.py:203:59: PLR2004 Magic value used in comparison, consider replacing `5` with a constant variable
    |
201 |         assert call_kwargs["engines"] == ["bing-scraper"]
202 |         assert call_kwargs["num_results"] == 10
203 |         assert call_kwargs["bing_scraper_max_retries"] == 5
    |                                                           ^ PLR2004
204 |         assert call_kwargs["bing_scraper_delay_between_requests"] == 2.0
    |

tests/web/test_bing_scraper.py:204:70: PLR2004 Magic value used in comparison, consider replacing `2.0` with a constant variable
    |
202 |         assert call_kwargs["num_results"] == 10
203 |         assert call_kwargs["bing_scraper_max_retries"] == 5
204 |         assert call_kwargs["bing_scraper_delay_between_requests"] == 2.0
    |                                                                      ^^^ PLR2004
205 |
206 |     @patch("twat_search.web.engines.bing_scraper.BingScraper")
    |

tests/web/test_bing_scraper.py:209:15: N803 Argument name `mock_BingScraper` should be lowercase
    |
207 |     @pytest.mark.asyncio
208 |     async def test_empty_query(
209 |         self, mock_BingScraper: MagicMock, engine: BingScraperSearchEngine
    |               ^^^^^^^^^^^^^^^^^^^^^^^^^^^ N803
210 |     ) -> None:
211 |         """Test behavior with empty query string."""
    |

tests/web/test_bing_scraper.py:222:15: N803 Argument name `mock_BingScraper` should be lowercase
    |
220 |     @pytest.mark.asyncio
221 |     async def test_no_results(
222 |         self, mock_BingScraper: MagicMock, engine: BingScraperSearchEngine
    |               ^^^^^^^^^^^^^^^^^^^^^^^^^^^ N803
223 |     ) -> None:
224 |         """Test handling of no results returned from BingScraper."""
    |

tests/web/test_bing_scraper.py:238:15: N803 Argument name `mock_BingScraper` should be lowercase
    |
236 |     @pytest.mark.asyncio
237 |     async def test_network_error(
238 |         self, mock_BingScraper: MagicMock, engine: BingScraperSearchEngine
    |               ^^^^^^^^^^^^^^^^^^^^^^^^^^^ N803
239 |     ) -> None:
240 |         """Test handling of network errors."""
    |

tests/web/test_bing_scraper.py:255:15: N803 Argument name `mock_BingScraper` should be lowercase
    |
253 |     @pytest.mark.asyncio
254 |     async def test_parsing_error(
255 |         self, mock_BingScraper: MagicMock, engine: BingScraperSearchEngine
    |               ^^^^^^^^^^^^^^^^^^^^^^^^^^^ N803
256 |     ) -> None:
257 |         """Test handling of parsing errors."""
    |

tests/web/test_bing_scraper.py:272:15: N803 Argument name `mock_BingScraper` should be lowercase
    |
270 |     @pytest.mark.asyncio
271 |     async def test_invalid_result_format(
272 |         self, mock_BingScraper: MagicMock, engine: BingScraperSearchEngine
    |               ^^^^^^^^^^^^^^^^^^^^^^^^^^^ N803
273 |     ) -> None:
274 |         """Test handling of invalid result format."""
    |

Found 253 errors.

2025-02-26 10:12:27 - 36 files left unchanged

2025-02-26 10:12:27 - >>>Running type checks...
2025-02-26 10:12:38 - src/twat_search/web/engines/searchit.py:24: error: Cannot find implementation or library stub for module named "searchit"  [import-not-found]
src/twat_search/web/engines/searchit.py:31: error: Cannot find implementation or library stub for module named "searchit.scrapers.scraper"  [import-not-found]
src/twat_search/web/engines/google_scraper.py:24: error: Cannot find implementation or library stub for module named "googlesearch"  [import-not-found]
src/twat_search/web/engines/duckduckgo.py:16: error: Cannot find implementation or library stub for module named "duckduckgo_search"  [import-not-found]
src/twat_search/web/engines/bing_scraper.py:24: error: Cannot find implementation or library stub for module named "scrape_bing"  [import-not-found]
src/twat_search/web/engines/anywebsearch.py:24: error: Cannot find implementation or library stub for module named "anywebsearch"  [import-not-found]
src/twat_search/web/engines/anywebsearch.py:25: error: Cannot find implementation or library stub for module named "anywebsearch.tools"  [import-not-found]
src/twat_search/web/engines/anywebsearch.py:25: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports
src/twat_search/web/engines/anywebsearch.py:71: error: Unused "type: ignore" comment  [unused-ignore]
tests/conftest.py:54: error: Only concrete class can be given where "type[SearchEngine]" is expected  [type-abstract]
src/twat_search/web/engines/you.py:65: error: "type[YouBaseEngine]" has no attribute "num_results_param"  [attr-defined]
src/twat_search/web/engines/you.py:78: error: "type[YouBaseEngine]" has no attribute "num_results_param"  [attr-defined]
src/twat_search/web/engines/you.py:89: error: "type[YouBaseEngine]" has no attribute "base_url"  [attr-defined]
src/twat_search/web/engines/you.py:95: error: Returning Any from function declared to return "dict[Any, Any]"  [no-any-return]
src/twat_search/web/engines/tavily.py:78: error: Function is missing a type annotation  [no-untyped-def]
src/twat_search/web/engines/brave.py:116: error: "BaseModel" has no attribute "description"  [attr-defined]
src/twat_search/web/engines/brave.py:126: error: "BaseModel" has no attribute "title"  [attr-defined]
src/twat_search/web/engines/brave.py:127: error: "BaseModel" has no attribute "url"  [attr-defined]
src/twat_search/web/engines/__init__.py:50: error: Incompatible types in assignment (expression has type "Callable[[str, int, str | None, str | None, bool | str | None, str | None, str | None, str | None], Coroutine[Any, Any, list[SearchResult]]]", target has type "Callable[[str, int, str | None, str | None, bool | str | None, str | None, str | None], Coroutine[Any, Any, list[SearchResult]]]")  [assignment]
src/twat_search/web/engines/__init__.py:58: error: Incompatible types in assignment (expression has type "Callable[[str, int, str | None, str | None, bool | None, str | None, str, list[str] | None, list[str] | None, bool, int | None, str, str | None], Coroutine[Any, Any, list[SearchResult]]]", target has type "Callable[[str, int, str | None, str | None, bool | str | None, str | None, str | None], Coroutine[Any, Any, list[SearchResult]]]")  [assignment]
src/twat_search/web/engines/__init__.py:66: error: Incompatible types in assignment (expression has type "Callable[[str, int, str | None, str | None, bool | None, str | None, str | None, str | None], Coroutine[Any, Any, list[SearchResult]]]", target has type "Callable[[str, int, str | None, str | None, bool | str | None, str | None, str | None], Coroutine[Any, Any, list[SearchResult]]]")  [assignment]
src/twat_search/web/engines/__init__.py:74: error: Incompatible types in assignment (expression has type "Callable[[str, int, str | None, str | None, bool | None, str | None, str | None], Coroutine[Any, Any, list[SearchResult]]]", target has type "Callable[[str, int, str | None, str | None, bool | str | None, str | None, str | None], Coroutine[Any, Any, list[SearchResult]]]")  [assignment]
src/twat_search/web/engines/__init__.py:75: error: Incompatible types in assignment (expression has type "Callable[[str, int, str | None, str | None, bool | None, str | None, str | None], Coroutine[Any, Any, list[SearchResult]]]", target has type "Callable[[str, int, str | None, str | None, bool | str | None, str | None, str | None], Coroutine[Any, Any, list[SearchResult]]]")  [assignment]
src/twat_search/web/engines/__init__.py:83: error: Incompatible types in assignment (expression has type "Callable[[str, int, str | None, str | None, bool | None, str | None, str | None, str | None, list[str] | None, list[str] | None, dict[str, Any] | None, str | None], Coroutine[Any, Any, list[SearchResult]]]", target has type "Callable[[str, int, str | None, str | None, bool | str | None, str | None, str | None], Coroutine[Any, Any, list[SearchResult]]]")  [assignment]
src/twat_search/web/engines/__init__.py:91: error: Incompatible types in assignment (expression has type "Callable[[str, int, str | None, str | None, bool, str | None, str | None, int, KwArg(Any)], Coroutine[Any, Any, list[SearchResult]]]", target has type "Callable[[str, int, str | None, str | None, bool | str | None, str | None, str | None], Coroutine[Any, Any, list[SearchResult]]]")  [assignment]
src/twat_search/web/engines/__init__.py:99: error: Incompatible types in assignment (expression has type "Callable[[str, int, int, float, KwArg(Any)], Coroutine[Any, Any, list[SearchResult]]]", target has type "Callable[[str, int, str | None, str | None, bool | str | None, str | None, str | None], Coroutine[Any, Any, list[SearchResult]]]")  [assignment]
src/twat_search/web/engines/__init__.py:113: error: Incompatible types in assignment (expression has type "Callable[[str, int, str | None, str, str | None], Coroutine[Any, Any, list[SearchResult]]]", target has type "Callable[[str, int, str | None, str | None, bool | str | None, str | None, str | None], Coroutine[Any, Any, list[SearchResult]]]")  [assignment]
src/twat_search/web/engines/__init__.py:114: error: Incompatible types in assignment (expression has type "Callable[[str, int, str | None, str | None], Coroutine[Any, Any, list[SearchResult]]]", target has type "Callable[[str, int, str | None, str | None, bool | str | None, str | None, str | None], Coroutine[Any, Any, list[SearchResult]]]")  [assignment]
tests/web/test_bing_scraper.py:281: error: Function is missing a return type annotation  [no-untyped-def]
tests/web/test_bing_scraper.py:281: note: Use "-> None" if function does not return a value
tests/test_twat_search.py:4: error: Function is missing a return type annotation  [no-untyped-def]
tests/test_twat_search.py:4: note: Use "-> None" if function does not return a value
Found 29 errors in 12 files (checked 37 source files)

2025-02-26 10:12:38 - >>> Running tests...
2025-02-26 10:12:40 - ============================= test session starts ==============================
platform darwin -- Python 3.12.8, pytest-8.3.4, pluggy-1.5.0 -- /Users/adam/Developer/vcs/github.twardoch/pub/twat-packages/_good/twat/plugins/repos/twat_search/.venv/bin/python
cachedir: .pytest_cache
benchmark: 5.1.0 (defaults: timer=time.perf_counter disable_gc=False min_rounds=5 min_time=0.000005 max_time=1.0 calibration_precision=10 warmup=False warmup_iterations=100000)
rootdir: /Users/adam/Developer/vcs/github.twardoch/pub/twat-packages/_good/twat/plugins/repos/twat_search
configfile: pyproject.toml
plugins: cov-6.0.0, asyncio-0.25.3, anyio-4.8.0, benchmark-5.1.0, xdist-3.6.1
asyncio: mode=Mode.AUTO, asyncio_default_fixture_loop_scope=None
collecting ... collected 49 items

tests/test_twat_search.py::test_version PASSED                           [  2%]
tests/unit/web/engines/test_base.py::test_search_engine_is_abstract PASSED [  4%]
tests/unit/web/engines/test_base.py::test_search_engine_name_class_var PASSED [  6%]
tests/unit/web/engines/test_base.py::test_engine_registration PASSED     [  8%]
tests/unit/web/engines/test_base.py::test_get_engine_with_invalid_name PASSED [ 10%]
tests/unit/web/engines/test_base.py::test_get_engine_with_disabled_engine PASSED [ 12%]
tests/unit/web/engines/test_base.py::test_get_engine_with_config PASSED  [ 14%]
tests/unit/web/engines/test_base.py::test_get_engine_with_kwargs PASSED  [ 16%]
tests/unit/web/test_api.py::test_search_with_mock_engine FAILED          [ 18%]
tests/unit/web/test_api.py::test_search_with_additional_params PASSED    [ 20%]
tests/unit/web/test_api.py::test_search_with_engine_specific_params PASSED [ 22%]
tests/unit/web/test_api.py::test_search_with_no_engines PASSED           [ 24%]
tests/unit/web/test_api.py::test_search_with_failing_engine PASSED       [ 26%]
tests/unit/web/test_api.py::test_search_with_nonexistent_engine PASSED   [ 28%]
tests/unit/web/test_api.py::test_search_with_disabled_engine PASSED      [ 30%]
tests/unit/web/test_config.py::test_engine_config_defaults PASSED        [ 32%]
tests/unit/web/test_config.py::test_engine_config_values PASSED          [ 34%]
tests/unit/web/test_config.py::test_config_defaults PASSED               [ 36%]
tests/unit/web/test_config.py::test_config_with_env_vars FAILED          [ 38%]
tests/unit/web/test_config.py::test_config_with_direct_initialization PASSED [ 40%]
tests/unit/web/test_config.py::test_config_env_vars_override_direct_config PASSED [ 42%]
tests/unit/web/test_exceptions.py::test_search_error PASSED              [ 44%]
tests/unit/web/test_exceptions.py::test_engine_error PASSED              [ 46%]
tests/unit/web/test_exceptions.py::test_engine_error_inheritance PASSED  [ 48%]
tests/unit/web/test_exceptions.py::test_search_error_as_base_class PASSED [ 51%]
tests/unit/web/test_models.py::test_search_result_valid_data PASSED      [ 53%]
tests/unit/web/test_models.py::test_search_result_with_optional_fields PASSED [ 55%]
tests/unit/web/test_models.py::test_search_result_invalid_url PASSED     [ 57%]
tests/unit/web/test_models.py::test_search_result_empty_fields PASSED    [ 59%]
tests/unit/web/test_models.py::test_search_result_serialization PASSED   [ 61%]
tests/unit/web/test_models.py::test_search_result_deserialization PASSED [ 63%]
tests/unit/web/test_utils.py::test_rate_limiter_init PASSED              [ 65%]
tests/unit/web/test_utils.py::test_rate_limiter_wait_when_not_needed PASSED [ 67%]
tests/unit/web/test_utils.py::test_rate_limiter_wait_when_needed PASSED  [ 69%]
tests/unit/web/test_utils.py::test_rate_limiter_cleans_old_timestamps PASSED [ 71%]
tests/unit/web/test_utils.py::test_rate_limiter_with_different_rates[1] PASSED [ 73%]
tests/unit/web/test_utils.py::test_rate_limiter_with_different_rates[5] PASSED [ 75%]
tests/unit/web/test_utils.py::test_rate_limiter_with_different_rates[10] PASSED [ 77%]
tests/unit/web/test_utils.py::test_rate_limiter_with_different_rates[100] PASSED [ 79%]
tests/web/test_bing_scraper.py::TestBingScraperEngine::test_init PASSED  [ 81%]
tests/web/test_bing_scraper.py::TestBingScraperEngine::test_search_basic PASSED [ 83%]
tests/web/test_bing_scraper.py::TestBingScraperEngine::test_custom_parameters PASSED [ 85%]
tests/web/test_bing_scraper.py::TestBingScraperEngine::test_invalid_url_handling PASSED [ 87%]
tests/web/test_bing_scraper.py::TestBingScraperEngine::test_bing_scraper_convenience_function PASSED [ 89%]
tests/web/test_bing_scraper.py::TestBingScraperEngine::test_empty_query PASSED [ 91%]
tests/web/test_bing_scraper.py::TestBingScraperEngine::test_no_results PASSED [ 93%]
tests/web/test_bing_scraper.py::TestBingScraperEngine::test_network_error PASSED [ 95%]
tests/web/test_bing_scraper.py::TestBingScraperEngine::test_parsing_error PASSED [ 97%]
tests/web/test_bing_scraper.py::TestBingScraperEngine::test_invalid_result_format PASSED [100%]

=================================== FAILURES ===================================
_________________________ test_search_with_mock_engine _________________________

mock_config = <twat_search.web.config.Config object at 0x111743b90>
setup_teardown = None

    @pytest.mark.asyncio
    async def test_search_with_mock_engine(
        mock_config: Config, setup_teardown: None
    ) -> None:
        """Test search with a mock engine."""
        results = await search("test query", engines=["mock"], config=mock_config)
    
>       assert len(results) == 2
E       AssertionError: assert 1 == 2
E        +  where 1 = len([SearchResult(title='Mock Result 1 for test query', url=HttpUrl('https://example.com/1'), snippet='This is mock result 1 for query: test query', source='mock', rank=None, raw=None)])

tests/unit/web/test_api.py:104: AssertionError
__________________________ test_config_with_env_vars ___________________________

monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x11173d070>
env_vars_for_brave = None

    def test_config_with_env_vars(
        monkeypatch: MonkeyPatch, env_vars_for_brave: None
    ) -> None:
        """Test Config loads settings from environment variables."""
        # Create config
        config = Config()
    
        # Check the brave engine was configured
        assert "brave" in config.engines
        brave_config = config.engines["brave"]
>       assert brave_config.api_key == "test_brave_key"
E       AssertionError: assert None == 'test_brave_key'
E        +  where None = EngineConfig(api_key=None, enabled=True, default_params={}).api_key

tests/unit/web/test_config.py:55: AssertionError
=========================== short test summary info ============================
FAILED tests/unit/web/test_api.py::test_search_with_mock_engine - AssertionEr...
FAILED tests/unit/web/test_config.py::test_config_with_env_vars - AssertionEr...
========================= 2 failed, 47 passed in 0.73s =========================

2025-02-26 10:12:40 - All checks completed
2025-02-26 10:12:40 - 
=== TODO.md ===
2025-02-26 10:12:40 - # twat-search Web Package - Future Tasks

The basic implementation of the `twat-search` web package is complete.

Tip: Periodically run `./cleanup.py status` to see results of lints and tests.

## 1. Phase 1

### 1.1. Complete Bing Scraper Implementation

- [ ] Fix implementation issues in bing_scraper.py
  - Add proper type annotations to all methods
  - Implement better error handling with appropriate context
- [ ] Complete test coverage for BingScraperSearchEngine
  - Fix skipped tests in test_bing_scraper.py
  - Add tests for error conditions and edge cases
- [ ] Document Bing Scraper functionality
  - Add comprehensive docstrings
  - Include usage examples in README


### 1.2. Documentation and Examples

- [ ] Add comprehensive docstrings to all classes and methods
  - Include parameter descriptions
  - Document exceptions that can be raised
  - Add usage examples
- [ ] Create detailed README examples
  - Basic usage examples for each engine
  - Advanced configuration examples
  - Error handling examples
- [ ] Document environment variable configuration
  - Create a comprehensive list of all supported environment variables
  - Add examples of .env file configuration


## 2. Phase 2

### 2.1. Type Checking Errors

- [ ] Fix missing type stubs for third-party modules
  - `duckduckgo_search` and `scrape_bing` are showing import-not-found errors
  - Create local stub files or install type stubs if available
- [ ] Add type annotations to functions missing them
  - Particularly in bing_scraper.py, need to add annotations to search methods
- [ ] Fix attribute errors in You.py engine
  - "YouBaseEngine" has no attribute errors for num_results_param and base_url
- [ ] Resolve incompatible types in engine assignments in **init**.py
- [ ] Fix the test_config_with_env_vars failure (api_key not being set correctly)

### 2.2. Linting Issues

- [ ] Address boolean parameter warnings (FBT001, FBT002)
  - Consider using keyword-only arguments for boolean parameters
  - Or create specific enum types for boolean options
- [ ] Fix functions with too many parameters (PLR0913)
  - Refactor using parameter objects or configuration objects
  - Consider breaking down complex functions
- [ ] Resolve magic values in code (PLR2004)
  - Replace hardcoded numbers like 100, 5, 10 with named constants
- [ ] Clean up unused imports (F401)
  - Remove or properly use imported modules

### 2.3. Improve Test Framework

- [ ] Implement mock search engines for all providers
  - Create standardized mock responses
  - Enable offline testing without API keys
- [ ] Add integration tests
  - Test the entire search workflow
  - Test concurrent searches across multiple engines
- [ ] Create test fixtures for common configurations
  - Standard API response data
  - Common error cases
- [ ] Fix test_config_with_env_vars failure
  - Debug why environment variables aren't being properly loaded

## 3. Phase 3

### 3.1. Enhance Engine Implementations

- [ ] Standardize error handling across all engines
  - Use consistent error context and messages
  - Properly propagate exceptions with 'from exc'
- [ ] Optimize parameter handling in engines
  - Reduce code duplication in parameter mapping
  - Create utility functions for common parameter conversions
- [ ] Add timeouts to all HTTP requests
  - Ensure all engines have consistent timeout handling
  - Add configurable timeout parameters

## 4. Phase 4

### 4.1. Additional Features

- [ ] Add result caching mechanism
  - Implement optional caching of search results
  - Add configurable cache expiration
- [ ] Implement rate limiting for all engines
  - Ensure all engines respect API rate limits
  - Add configurable backoff strategies
- [ ] Add result normalization
  - Create a more consistent result format across engines
  - Implement result scoring and ranking

### 4.2. Performance Improvements

- [ ] Profile search performance across engines
  - Measure latency and throughput
  - Identify performance bottlenecks
- [ ] Implement connection pooling for HTTP clients
  - Reuse connections where possible
  - Configure appropriate connection limits
- [ ] Add parallelization options for multiple searches
  - Control concurrency limits
  - Implement proper resource cleanup

2025-02-26 10:12:41 - 
📦 Repomix v0.2.29

No custom config found at repomix.config.json or global config at /Users/adam/.config/repomix/repomix.config.json.
You can add a config file for additional settings. Please check https://github.com/yamadashy/repomix for more information.
⠙ Collecting files...
[2K[1A[2K[G⠹ Collecting files...
[2K[1A[2K[G⠸ Collect file... (42/51) .gitignore
[2K[1A[2K[G⠼ Running security check...
[2K[1A[2K[G⠴ Processing files...
[2K[1A[2K[G⠦ Processing file... (5/50) .github/workflows/release.yml
[2K[1A[2K[G⠧ Processing file... (34/50) tests/unit/web/test_exceptions.py
[2K[1A[2K[G⠇ Calculating metrics...
[2K[1A[2K[G⠏ Calculating metrics...
[2K[1A[2K[G⠋ Calculating metrics...
[2K[1A[2K[G⠙ Calculating metrics... (8/50) src/twat_search/web/engines/base.py
[2K[1A[2K[G✔ Packing completed successfully!

📈 Top 5 Files by Character Count and Token Count:
──────────────────────────────────────────────────
1.  README.md (20,981 chars, 5,079 tokens)
2.  pyproject.toml (10,005 chars, 2,737 tokens)
3.  src/twat_search/web/cli.py (9,538 chars, 2,007 tokens)
4.  cleanup.py (5,868 chars, 1,301 tokens)
5.  src/twat_search/web/engines/searchit.py (4,617 chars, 1,012 tokens)

🔎 Security Check:
──────────────────
1 suspicious file(s) detected and excluded from the output:
1. NEXTENGINES.md
   - found basic auth credential: http://username:password@proxy.host.com
   - found basic auth credential: socks5://username:password@proxy.host.com

These files have been excluded from the output for security reasons.
Please review these files for potential sensitive information.

📊 Pack Summary:
────────────────
  Total Files: 50 files
  Total Chars: 120,731 chars
 Total Tokens: 29,176 tokens
       Output: twat_search.txt
     Security: 1 suspicious file(s) detected and excluded

🎉 All Done!
Your repository has been successfully packed.

💡 Repomix is now available in your browser! Try it at https://repomix.com

2025-02-26 10:12:41 - Repository content mixed into twat_search.txt
