switchback 0.6.2__tar.gz → 0.6.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 (54) hide show
  1. {switchback-0.6.2 → switchback-0.6.3}/CHANGELOG.md +9 -0
  2. {switchback-0.6.2 → switchback-0.6.3}/PKG-INFO +1 -1
  3. {switchback-0.6.2 → switchback-0.6.3}/pyproject.toml +1 -1
  4. {switchback-0.6.2 → switchback-0.6.3}/switchback/tiers/_browser.py +8 -0
  5. {switchback-0.6.2 → switchback-0.6.3}/switchback/tiers/tier_4.py +1 -1
  6. {switchback-0.6.2 → switchback-0.6.3}/switchback/tiers/tier_5.py +1 -1
  7. {switchback-0.6.2 → switchback-0.6.3}/switchback.egg-info/PKG-INFO +1 -1
  8. {switchback-0.6.2 → switchback-0.6.3}/.env.example +0 -0
  9. {switchback-0.6.2 → switchback-0.6.3}/CONTRIBUTING.md +0 -0
  10. {switchback-0.6.2 → switchback-0.6.3}/LICENSE +0 -0
  11. {switchback-0.6.2 → switchback-0.6.3}/MANIFEST.in +0 -0
  12. {switchback-0.6.2 → switchback-0.6.3}/NOTICE +0 -0
  13. {switchback-0.6.2 → switchback-0.6.3}/README.md +0 -0
  14. {switchback-0.6.2 → switchback-0.6.3}/SECURITY.md +0 -0
  15. {switchback-0.6.2 → switchback-0.6.3}/clients/node_bridge.md +0 -0
  16. {switchback-0.6.2 → switchback-0.6.3}/clients/python_client.py +0 -0
  17. {switchback-0.6.2 → switchback-0.6.3}/config/botwall_skip_urls.txt +0 -0
  18. {switchback-0.6.2 → switchback-0.6.3}/config/extraction.example.json +0 -0
  19. {switchback-0.6.2 → switchback-0.6.3}/setup.cfg +0 -0
  20. {switchback-0.6.2 → switchback-0.6.3}/switchback/__init__.py +0 -0
  21. {switchback-0.6.2 → switchback-0.6.3}/switchback/__main__.py +0 -0
  22. {switchback-0.6.2 → switchback-0.6.3}/switchback/api.py +0 -0
  23. {switchback-0.6.2 → switchback-0.6.3}/switchback/concurrency.py +0 -0
  24. {switchback-0.6.2 → switchback-0.6.3}/switchback/content_cache.py +0 -0
  25. {switchback-0.6.2 → switchback-0.6.3}/switchback/doctor.py +0 -0
  26. {switchback-0.6.2 → switchback-0.6.3}/switchback/egress.py +0 -0
  27. {switchback-0.6.2 → switchback-0.6.3}/switchback/extract.py +0 -0
  28. {switchback-0.6.2 → switchback-0.6.3}/switchback/flags.py +0 -0
  29. {switchback-0.6.2 → switchback-0.6.3}/switchback/highlights.py +0 -0
  30. {switchback-0.6.2 → switchback-0.6.3}/switchback/mcp_server.py +0 -0
  31. {switchback-0.6.2 → switchback-0.6.3}/switchback/normalize.py +0 -0
  32. {switchback-0.6.2 → switchback-0.6.3}/switchback/orchestrator.py +0 -0
  33. {switchback-0.6.2 → switchback-0.6.3}/switchback/policy/__init__.py +0 -0
  34. {switchback-0.6.2 → switchback-0.6.3}/switchback/policy/botwall.py +0 -0
  35. {switchback-0.6.2 → switchback-0.6.3}/switchback/policy/gates.py +0 -0
  36. {switchback-0.6.2 → switchback-0.6.3}/switchback/py.typed +0 -0
  37. {switchback-0.6.2 → switchback-0.6.3}/switchback/reporting.py +0 -0
  38. {switchback-0.6.2 → switchback-0.6.3}/switchback/search.py +0 -0
  39. {switchback-0.6.2 → switchback-0.6.3}/switchback/server.py +0 -0
  40. {switchback-0.6.2 → switchback-0.6.3}/switchback/session_cache.py +0 -0
  41. {switchback-0.6.2 → switchback-0.6.3}/switchback/session_trace.py +0 -0
  42. {switchback-0.6.2 → switchback-0.6.3}/switchback/state.py +0 -0
  43. {switchback-0.6.2 → switchback-0.6.3}/switchback/tiers/__init__.py +0 -0
  44. {switchback-0.6.2 → switchback-0.6.3}/switchback/tiers/tier_1.py +0 -0
  45. {switchback-0.6.2 → switchback-0.6.3}/switchback/tiers/tier_2.py +0 -0
  46. {switchback-0.6.2 → switchback-0.6.3}/switchback/tiers/tier_3.py +0 -0
  47. {switchback-0.6.2 → switchback-0.6.3}/switchback/tiers/tier_6.py +0 -0
  48. {switchback-0.6.2 → switchback-0.6.3}/switchback/tiers/tier_7.py +0 -0
  49. {switchback-0.6.2 → switchback-0.6.3}/switchback/tracing.py +0 -0
  50. {switchback-0.6.2 → switchback-0.6.3}/switchback.egg-info/SOURCES.txt +0 -0
  51. {switchback-0.6.2 → switchback-0.6.3}/switchback.egg-info/dependency_links.txt +0 -0
  52. {switchback-0.6.2 → switchback-0.6.3}/switchback.egg-info/entry_points.txt +0 -0
  53. {switchback-0.6.2 → switchback-0.6.3}/switchback.egg-info/requires.txt +0 -0
  54. {switchback-0.6.2 → switchback-0.6.3}/switchback.egg-info/top_level.txt +0 -0
@@ -4,6 +4,15 @@ All notable changes to this project are documented here. Format loosely follows
4
4
  [Keep a Changelog](https://keepachangelog.com/); this project uses semantic-ish
5
5
  versioning while pre-1.0.
6
6
 
7
+ ## [0.6.3] - 2026-10-14
8
+
9
+ ### Fixed
10
+ - **"Page not found" pages returned as articles by the browser tiers.** tier_4
11
+ and tier_5 ignored the HTTP status of the page they loaded, so a site answering
12
+ 404 (seen on bhaskarenglish.in, which serves its 404 page to some bot traffic)
13
+ had its "404 - Page not found" page returned as a success. A 404 or 410 now
14
+ fails the tier, as it already did in tier_2, and the cascade moves on.
15
+
7
16
  ## [0.6.2] - 2026-10-14
8
17
 
9
18
  ### Fixed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: switchback
3
- Version: 0.6.2
3
+ Version: 0.6.3
4
4
  Summary: One cost-ordered scrape cascade (HTTP → stealth browser → paid), shared by every tool.
5
5
  Author-email: Akash Kodavuru <akash@theaklabs.com>
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "switchback"
7
- version = "0.6.2"
7
+ version = "0.6.3"
8
8
  description = "One cost-ordered scrape cascade (HTTP → stealth browser → paid), shared by every tool."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -34,6 +34,14 @@ def looks_blocked(html: str, url: str) -> bool:
34
34
  return _looks_like_botwall(html_to_markdown(html, base_url=url))
35
35
 
36
36
 
37
+ def raise_if_missing(response) -> None:
38
+ """Fail the tier on a 404/410 document, as tier_2 does; a browser otherwise
39
+ renders the site's "Page not found" page and returns it as the article."""
40
+ status = getattr(response, "status", None)
41
+ if status in (404, 410):
42
+ raise RuntimeError(f"HTTP Error {status}: page not found")
43
+
44
+
37
45
  def response_bytes(responses) -> int:
38
46
  """Total wire bytes a render pulled across every resource — the residential-cost
39
47
  basis. Reads only the Content-Length header: it's non-blocking. (We deliberately
@@ -80,7 +80,7 @@ def fetch(url: str, timeout_ms: int = int(_TIMEOUT_S * 1000)) -> str:
80
80
  page = ctx.new_page()
81
81
  responses: list = []
82
82
  page.on("response", lambda resp: responses.append(resp))
83
- page.goto(url, timeout=timeout_ms, wait_until="domcontentloaded")
83
+ _browser.raise_if_missing(page.goto(url, timeout=timeout_ms, wait_until="domcontentloaded"))
84
84
  html = page.content()
85
85
  if len(html) < 5000:
86
86
  try:
@@ -86,7 +86,7 @@ def fetch(url: str) -> str:
86
86
  auth = session_cache.browser_cookies(url)
87
87
  if auth:
88
88
  page.context.add_cookies(auth)
89
- page.goto(url, wait_until="networkidle", timeout=_TIMEOUT_MS)
89
+ _browser.raise_if_missing(page.goto(url, wait_until="networkidle", timeout=_TIMEOUT_MS))
90
90
  html = page.content()
91
91
  # JS bot-manager sensor interstitial → settle + reload once.
92
92
  if _browser.looks_blocked(html, url):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: switchback
3
- Version: 0.6.2
3
+ Version: 0.6.3
4
4
  Summary: One cost-ordered scrape cascade (HTTP → stealth browser → paid), shared by every tool.
5
5
  Author-email: Akash Kodavuru <akash@theaklabs.com>
6
6
  License: MIT
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