blopus 0.3.2__tar.gz → 0.3.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. {blopus-0.3.2 → blopus-0.3.3}/PKG-INFO +1 -1
  2. {blopus-0.3.2 → blopus-0.3.3}/blopus/_async.py +6 -0
  3. {blopus-0.3.2 → blopus-0.3.3}/blopus/_common.py +11 -0
  4. {blopus-0.3.2 → blopus-0.3.3}/blopus/_version.py +1 -1
  5. {blopus-0.3.2 → blopus-0.3.3}/blopus/cli.py +23 -1
  6. {blopus-0.3.2 → blopus-0.3.3}/blopus/client.py +6 -0
  7. {blopus-0.3.2 → blopus-0.3.3}/blopus/crewai.py +11 -2
  8. {blopus-0.3.2 → blopus-0.3.3}/blopus/llamaindex.py +2 -1
  9. {blopus-0.3.2 → blopus-0.3.3}/blopus/models.py +10 -0
  10. {blopus-0.3.2 → blopus-0.3.3}/blopus.egg-info/PKG-INFO +1 -1
  11. {blopus-0.3.2 → blopus-0.3.3}/pyproject.toml +1 -1
  12. {blopus-0.3.2 → blopus-0.3.3}/LICENSE +0 -0
  13. {blopus-0.3.2 → blopus-0.3.3}/README.md +0 -0
  14. {blopus-0.3.2 → blopus-0.3.3}/blopus/__init__.py +0 -0
  15. {blopus-0.3.2 → blopus-0.3.3}/blopus/exceptions.py +0 -0
  16. {blopus-0.3.2 → blopus-0.3.3}/blopus/langchain.py +0 -0
  17. {blopus-0.3.2 → blopus-0.3.3}/blopus/mcp.py +0 -0
  18. {blopus-0.3.2 → blopus-0.3.3}/blopus.egg-info/SOURCES.txt +0 -0
  19. {blopus-0.3.2 → blopus-0.3.3}/blopus.egg-info/dependency_links.txt +0 -0
  20. {blopus-0.3.2 → blopus-0.3.3}/blopus.egg-info/entry_points.txt +0 -0
  21. {blopus-0.3.2 → blopus-0.3.3}/blopus.egg-info/requires.txt +0 -0
  22. {blopus-0.3.2 → blopus-0.3.3}/blopus.egg-info/top_level.txt +0 -0
  23. {blopus-0.3.2 → blopus-0.3.3}/setup.cfg +0 -0
  24. {blopus-0.3.2 → blopus-0.3.3}/tests/test_client.py +0 -0
  25. {blopus-0.3.2 → blopus-0.3.3}/tests/test_common.py +0 -0
  26. {blopus-0.3.2 → blopus-0.3.3}/tests/test_mcp.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: blopus
3
- Version: 0.3.2
3
+ Version: 0.3.3
4
4
  Summary: Official Python SDK for the Blopus web search + fetch API.
5
5
  Author: Blopus
6
6
  License: MIT
@@ -93,6 +93,9 @@ class AsyncBlopus:
93
93
  include_excerpt: bool = False,
94
94
  excerpt_chars: Optional[int] = None,
95
95
  news_only: bool = False,
96
+ recency: Optional[str] = None,
97
+ include_content: bool = False,
98
+ content_chars: Optional[int] = None,
96
99
  ) -> SearchResponse:
97
100
  """Run a web search. Always costs 1 credit regardless of params.
98
101
 
@@ -120,6 +123,9 @@ class AsyncBlopus:
120
123
  include_excerpt=include_excerpt,
121
124
  excerpt_chars=excerpt_chars,
122
125
  news_only=news_only,
126
+ recency=recency,
127
+ include_content=include_content,
128
+ content_chars=content_chars,
123
129
  )
124
130
  return SearchResponse.from_dict(await self._post("/v1/search", body))
125
131
 
@@ -110,6 +110,9 @@ def build_search_body(
110
110
  include_excerpt: bool = False,
111
111
  excerpt_chars: Optional[int] = None,
112
112
  news_only: bool = False,
113
+ recency: Optional[str] = None,
114
+ include_content: bool = False,
115
+ content_chars: Optional[int] = None,
113
116
  ) -> Dict[str, Any]:
114
117
  """Assemble the JSON body for ``POST /v1/search`` (omitting unset optionals)."""
115
118
  body: Dict[str, Any] = {
@@ -132,6 +135,14 @@ def build_search_body(
132
135
  body["include_excerpt"] = True
133
136
  if excerpt_chars is not None:
134
137
  body["excerpt_chars"] = excerpt_chars
138
+ # recency changes RANKING; freshness FILTERS. Sent only when set, so the server
139
+ # default ("normal") stays authoritative.
140
+ if recency is not None:
141
+ body["recency"] = recency
142
+ if include_content:
143
+ body["include_content"] = True
144
+ if content_chars is not None:
145
+ body["content_chars"] = content_chars
135
146
  if news_only:
136
147
  # only send it when true — an older gateway would 422 on an unknown field
137
148
  body["news_only"] = True
@@ -5,4 +5,4 @@ header, so a correct value here is what keeps Cloudflare from 1010-blocking
5
5
  requests as an unidentified library.
6
6
  """
7
7
 
8
- __version__ = "0.3.2"
8
+ __version__ = "0.3.3"
@@ -175,6 +175,12 @@ def _cmd_search(args: argparse.Namespace) -> int:
175
175
  language=args.language,
176
176
  offset=args.offset,
177
177
  include_excerpt=args.include_excerpt,
178
+ excerpt_chars=args.excerpt_chars,
179
+ start_date=args.start_date,
180
+ end_date=args.end_date,
181
+ recency=args.recency,
182
+ include_content=args.include_content,
183
+ content_chars=args.content_chars,
178
184
  )
179
185
  if args.json:
180
186
  print(json.dumps(res.raw, indent=2, ensure_ascii=False))
@@ -184,7 +190,9 @@ def _cmd_search(args: argparse.Namespace) -> int:
184
190
  for i, r in enumerate(res.results, 1):
185
191
  print(f"{i}. {r.title}")
186
192
  print(f" {r.url}")
187
- if r.snippet:
193
+ if r.content:
194
+ print(f" {r.content}")
195
+ elif r.snippet:
188
196
  print(f" {r.snippet}")
189
197
  print()
190
198
  if res.remaining_quota is not None:
@@ -298,6 +306,20 @@ def build_parser() -> argparse.ArgumentParser:
298
306
  help="Skip N results for pagination (0-200).")
299
307
  s.add_argument("--include-excerpt", action="store_true",
300
308
  help="Return a longer excerpt per result (skips a separate fetch).")
309
+ s.add_argument("--recency", default=None, choices=["normal", "relaxed", "off"],
310
+ help="Ranking preference, not a filter. 'off' for timeless questions "
311
+ "where the best answer may be months old.")
312
+ s.add_argument("--start-date", default=None, metavar="DATE",
313
+ help="Window start: YYYY-MM-DD or epoch seconds.")
314
+ s.add_argument("--end-date", default=None, metavar="DATE",
315
+ help="Window end: YYYY-MM-DD or epoch seconds.")
316
+ s.add_argument("--excerpt-chars", type=int, default=None, metavar="N",
317
+ help="Excerpt length when --include-excerpt is set.")
318
+ s.add_argument("--include-content", action="store_true",
319
+ help="Return full text inline instead of a snippet. Costs the same as "
320
+ "the search, so it beats fetching each result separately.")
321
+ s.add_argument("--content-chars", type=int, default=None, metavar="N",
322
+ help="Cap inline content length per result.")
301
323
  s.add_argument("--json", action="store_true", help="Emit raw JSON instead of text.")
302
324
  s.set_defaults(func=_cmd_search)
303
325
 
@@ -105,6 +105,9 @@ class Blopus:
105
105
  include_excerpt: bool = False,
106
106
  excerpt_chars: Optional[int] = None,
107
107
  news_only: bool = False,
108
+ recency: Optional[str] = None,
109
+ include_content: bool = False,
110
+ content_chars: Optional[int] = None,
108
111
  ) -> SearchResponse:
109
112
  """Run a web search. Always costs 1 credit regardless of params.
110
113
 
@@ -132,6 +135,9 @@ class Blopus:
132
135
  include_excerpt=include_excerpt,
133
136
  excerpt_chars=excerpt_chars,
134
137
  news_only=news_only,
138
+ recency=recency,
139
+ include_content=include_content,
140
+ content_chars=content_chars,
135
141
  )
136
142
  return SearchResponse.from_dict(self._post("/v1/search", body))
137
143
 
@@ -34,8 +34,16 @@ def BlopusSearchTool(
34
34
  client: Optional[Blopus] = None,
35
35
  count: int = 10,
36
36
  freshness: str = "all",
37
+ news_only: bool = False,
38
+ **search_kwargs,
37
39
  ):
38
- """Return a CrewAI ``BaseTool`` backed by ``Blopus.search``."""
40
+ """Return a CrewAI ``BaseTool`` backed by ``Blopus.search``.
41
+
42
+ ``news_only`` and any other :meth:`Blopus.search` keyword are accepted so this
43
+ adapter is not the odd one out — the LangChain and LlamaIndex wrappers already
44
+ take them, and a parameter missing from one framework's wrapper is a parameter
45
+ that framework's users cannot reach.
46
+ """
39
47
  BaseTool, BaseModel, Field = _requirements()
40
48
  _client = client or Blopus(api_key=api_key)
41
49
 
@@ -51,7 +59,8 @@ def BlopusSearchTool(
51
59
  args_schema: type = _Schema
52
60
 
53
61
  def _run(self, query: str) -> str:
54
- res = _client.search(query, count=count, freshness=freshness)
62
+ res = _client.search(query, count=count, freshness=freshness,
63
+ news_only=news_only, **search_kwargs)
55
64
  if not res.results:
56
65
  return "No results."
57
66
  return "\n".join(
@@ -39,6 +39,7 @@ def BlopusToolSpec(
39
39
  count: int = 10,
40
40
  freshness: str = "all",
41
41
  news_only: bool = False,
42
+ **search_kwargs,
42
43
  ):
43
44
  """Return a LlamaIndex tool spec exposing Blopus ``search`` and ``fetch``.
44
45
 
@@ -55,7 +56,7 @@ def BlopusToolSpec(
55
56
  def blopus_search(self, query: str) -> List[dict]:
56
57
  """Search the web. Returns a list of {title, url, snippet, score} results."""
57
58
  res = _client.search(query, count=count, freshness=freshness,
58
- news_only=news_only)
59
+ news_only=news_only, **search_kwargs)
59
60
  return [
60
61
  {"title": r.title, "url": r.url, "snippet": r.snippet, "score": r.score}
61
62
  for r in res.results
@@ -41,6 +41,12 @@ class SearchResult:
41
41
  age_seconds: Optional[int] = None
42
42
  language: Optional[str] = None
43
43
  score: Optional[float] = None
44
+ #: Crawl time. ``published_at`` falls back to this when the article date is unknown.
45
+ fetched_at: Optional[int] = None
46
+ #: How many near-identical pages were collapsed into this hit.
47
+ duplicate_count: int = 0
48
+ #: Full text. Present only when the request set ``include_content``.
49
+ content: Optional[str] = None
44
50
  raw: Dict[str, Any] = field(default_factory=dict, repr=False)
45
51
 
46
52
  @classmethod
@@ -56,6 +62,10 @@ class SearchResult:
56
62
  age_seconds=_as_int(d.get("age_seconds")),
57
63
  language=d.get("language"),
58
64
  score=_as_float(d.get("score")),
65
+ fetched_at=_as_int(d.get("fetched_at")),
66
+ duplicate_count=_as_int(d.get("duplicate_count")) or 0,
67
+ # only present when include_content was requested
68
+ content=d.get("content"),
59
69
  raw=d,
60
70
  )
61
71
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: blopus
3
- Version: 0.3.2
3
+ Version: 0.3.3
4
4
  Summary: Official Python SDK for the Blopus web search + fetch API.
5
5
  Author: Blopus
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "blopus"
7
- version = "0.3.2"
7
+ version = "0.3.3"
8
8
  description = "Official Python SDK for the Blopus web search + fetch API."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.8"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes