Spaces:
Sleeping
Sleeping
| import os | |
| import random | |
| import time | |
| import requests | |
| from .logger import get_logger | |
| logger = get_logger(__name__) | |
| _BRAVE_URL = "https://api.search.brave.com/res/v1/web/search" | |
| _MAX_RETRIES = 3 | |
| def _brave_request( | |
| params: dict, | |
| api_key: str, | |
| accept: str = "application/json", | |
| ) -> requests.Response | None: | |
| """Send a Brave Search request with retry + exponential backoff on 429. | |
| A small random jitter (0-2 s) is added before the first attempt to | |
| spread out concurrent requests from parallel region fan-out. | |
| """ | |
| headers = { | |
| "Accept": accept, | |
| "X-Subscription-Token": api_key, | |
| } | |
| time.sleep(random.uniform(0, 2.0)) | |
| for attempt in range(_MAX_RETRIES): | |
| try: | |
| resp = requests.get( | |
| _BRAVE_URL, headers=headers, params=params, timeout=15, | |
| ) | |
| if resp.status_code == 429: | |
| wait = (2 ** (attempt + 1)) + random.uniform(0, 1) | |
| logger.info( | |
| "Brave 429 (attempt %d/%d), retrying in %.1fs…", | |
| attempt + 1, _MAX_RETRIES, wait, | |
| ) | |
| time.sleep(wait) | |
| continue | |
| resp.raise_for_status() | |
| return resp | |
| except requests.exceptions.HTTPError: | |
| raise | |
| except requests.exceptions.RequestException as exc: | |
| if attempt < _MAX_RETRIES - 1: | |
| wait = (2 ** (attempt + 1)) + random.uniform(0, 1) | |
| logger.info("Brave network error, retrying in %.1fs: %s", wait, exc) | |
| time.sleep(wait) | |
| else: | |
| raise | |
| return None | |
| def brave_search(query: str, count: int = 5, freshness: str = "") -> str: | |
| """Single implementation of Brave Search used across the whole project. | |
| Args: | |
| query: The search query string. | |
| count: Number of results to request (max 20). | |
| freshness: Optional freshness filter (e.g. ``"pw"`` for past week, | |
| ``"pm"`` for past month). | |
| Returns: | |
| A newline-joined string of ``"- title: description"`` lines, or an | |
| error message. | |
| """ | |
| api_key = os.getenv("BRAVE_API_KEY") | |
| if not api_key: | |
| logger.warning("BRAVE_API_KEY not set – search skipped") | |
| return "No Brave API key found." | |
| params: dict = {"q": query, "count": count} | |
| if freshness: | |
| params["freshness"] = freshness | |
| try: | |
| resp = _brave_request(params, api_key) | |
| if resp is None: | |
| logger.warning("Brave rate-limit exhausted for query: %s", query) | |
| return "Rate limit hit – try again shortly." | |
| results = resp.json().get("web", {}).get("results", []) | |
| if not results: | |
| logger.info("Brave returned 0 results for: %s", query) | |
| return "No results found." | |
| return "\n".join( | |
| f"- {r.get('title', '')}: {r.get('description', '')}" | |
| for r in results | |
| ) | |
| except requests.exceptions.HTTPError as exc: | |
| if exc.response is not None and exc.response.status_code == 429: | |
| logger.warning("Brave rate-limit hit for query: %s", query) | |
| return "Rate limit hit – try again shortly." | |
| logger.error("Brave HTTP error: %s", exc) | |
| return f"Search HTTP error: {exc}" | |
| except requests.exceptions.RequestException as exc: | |
| logger.error("Brave network error: %s", exc) | |
| return f"Search error: {exc}" | |
| def brave_search_raw(query: str, count: int = 10, freshness: str = "pm") -> str: | |
| """Return the raw combined text of titles + descriptions. | |
| Useful for regex-based ticker extraction in niche_hunter / scanner. | |
| """ | |
| api_key = os.getenv("BRAVE_API_KEY") | |
| if not api_key: | |
| logger.warning("BRAVE_API_KEY not set – raw search skipped") | |
| return "" | |
| params: dict = {"q": query, "count": count} | |
| if freshness: | |
| params["freshness"] = freshness | |
| try: | |
| resp = _brave_request(params, api_key) | |
| if resp is None: | |
| logger.warning("Brave rate-limit exhausted for raw query") | |
| return "" | |
| results = resp.json().get("web", {}).get("results", []) | |
| return " ".join(f"{r['title']} {r['description']}" for r in results) | |
| except requests.exceptions.RequestException as exc: | |
| logger.error("Brave raw-search error: %s", exc) | |
| return "" | |