unique-search-proxy 2026.44.0.dev8__tar.gz → 2026.44.0.dev9__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 (118) hide show
  1. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/PKG-INFO +2 -2
  2. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/pyproject.toml +2 -2
  3. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/core/crawlers/basic/service.py +63 -6
  4. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/README.md +0 -0
  5. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/__init__.py +0 -0
  6. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/__init__.py +0 -0
  7. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/api/__init__.py +0 -0
  8. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/api/health.py +0 -0
  9. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/api/v1/__init__.py +0 -0
  10. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/api/v1/agent_search.py +0 -0
  11. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/api/v1/configuration.py +0 -0
  12. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/api/v1/context_headers.py +0 -0
  13. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/api/v1/crawl.py +0 -0
  14. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/api/v1/openapi_examples.py +0 -0
  15. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/api/v1/search.py +0 -0
  16. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/app.py +0 -0
  17. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/context.py +0 -0
  18. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/core/__init__.py +0 -0
  19. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/core/agent_engines/__init__.py +0 -0
  20. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/core/agent_engines/bing/cleanup.py +0 -0
  21. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/core/agent_engines/bing/client.py +0 -0
  22. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/core/agent_engines/bing/runner.py +0 -0
  23. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/core/agent_engines/bing/service.py +0 -0
  24. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/core/agent_engines/factory.py +0 -0
  25. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/core/agent_engines/service_base.py +0 -0
  26. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/core/agent_engines/structured_output.py +0 -0
  27. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/core/agent_engines/vertexai/client.py +0 -0
  28. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/core/agent_engines/vertexai/gemini.py +0 -0
  29. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/core/agent_engines/vertexai/redirects.py +0 -0
  30. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/core/agent_engines/vertexai/service.py +0 -0
  31. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/core/client/__init__.py +0 -0
  32. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/core/client/azure_transport.py +0 -0
  33. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/core/client/credentials.py +0 -0
  34. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/core/client/service.py +0 -0
  35. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/core/crawlers/__init__.py +0 -0
  36. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/core/crawlers/basic/__init__.py +0 -0
  37. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/core/crawlers/basic/processing/__init__.py +0 -0
  38. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/core/crawlers/basic/processing/errors.py +0 -0
  39. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/core/crawlers/basic/processing/html_markdown.py +0 -0
  40. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/core/crawlers/basic/processing/processors/__init__.py +0 -0
  41. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/core/crawlers/basic/processing/processors/html.py +0 -0
  42. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/core/crawlers/basic/processing/processors/pdf.py +0 -0
  43. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/core/crawlers/basic/processing/processors/plain_text.py +0 -0
  44. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/core/crawlers/basic/processing/registry.py +0 -0
  45. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/core/crawlers/basic/settings.py +0 -0
  46. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/core/crawlers/basic/user_agent.py +0 -0
  47. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/core/crawlers/factory.py +0 -0
  48. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/core/crawlers/firecrawl/polling.py +0 -0
  49. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/core/crawlers/firecrawl/request_body.py +0 -0
  50. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/core/crawlers/firecrawl/service.py +0 -0
  51. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/core/crawlers/jina/request_body.py +0 -0
  52. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/core/crawlers/jina/service.py +0 -0
  53. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/core/crawlers/pinned_egress.py +0 -0
  54. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/core/crawlers/tavily/request_body.py +0 -0
  55. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/core/crawlers/tavily/service.py +0 -0
  56. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/core/provider_response.py +0 -0
  57. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/core/providers.py +0 -0
  58. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/core/registry.py +0 -0
  59. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/core/search_engines/__init__.py +0 -0
  60. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/core/search_engines/brave/__init__.py +0 -0
  61. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/core/search_engines/brave/pagination.py +0 -0
  62. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/core/search_engines/brave/query_params.py +0 -0
  63. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/core/search_engines/brave/service.py +0 -0
  64. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/core/search_engines/custom_api/__init__.py +0 -0
  65. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/core/search_engines/custom_api/service.py +0 -0
  66. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/core/search_engines/descriptor.py +0 -0
  67. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/core/search_engines/factory.py +0 -0
  68. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/core/search_engines/google/__init__.py +0 -0
  69. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/core/search_engines/google/pagination.py +0 -0
  70. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/core/search_engines/google/query_params.py +0 -0
  71. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/core/search_engines/google/service.py +0 -0
  72. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/core/search_engines/pagination.py +0 -0
  73. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/core/search_engines/perplexity/__init__.py +0 -0
  74. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/core/search_engines/perplexity/request_body.py +0 -0
  75. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/core/search_engines/perplexity/service.py +0 -0
  76. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/core/search_engines/service_base.py +0 -0
  77. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/core/url_safety/__init__.py +0 -0
  78. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/core/url_safety/gate.py +0 -0
  79. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/error_handlers.py +0 -0
  80. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/helm/__init__.py +0 -0
  81. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/helm/generator/__init__.py +0 -0
  82. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/helm/generator/introspect.py +0 -0
  83. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/helm/generator/schema.py +0 -0
  84. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/helm/generator/template.py +0 -0
  85. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/helm/generator/templates/provider_values_yaml.j2 +0 -0
  86. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/helm/generator/values_yaml.py +0 -0
  87. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/helm/metadata.py +0 -0
  88. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/helm/registry.py +0 -0
  89. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/helm/url_safety_registration.py +0 -0
  90. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/logging_config.py +0 -0
  91. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/middleware/context.py +0 -0
  92. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/monitoring/__init__.py +0 -0
  93. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/monitoring/metrics.py +0 -0
  94. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/monitoring/setup.py +0 -0
  95. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/presets/__init__.py +0 -0
  96. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/presets/common.py +0 -0
  97. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/presets/crawl.py +0 -0
  98. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/presets/search.py +0 -0
  99. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/presets/types.py +0 -0
  100. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/settings/__init__.py +0 -0
  101. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/settings/app.py +0 -0
  102. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/settings/base.py +0 -0
  103. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/settings/client.py +0 -0
  104. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/settings/monitoring.py +0 -0
  105. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/settings/providers/__init__.py +0 -0
  106. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/settings/providers/base.py +0 -0
  107. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/settings/providers/bing_agent.py +0 -0
  108. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/settings/providers/brave.py +0 -0
  109. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/settings/providers/firecrawl.py +0 -0
  110. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/settings/providers/google.py +0 -0
  111. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/settings/providers/jina.py +0 -0
  112. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/settings/providers/perplexity.py +0 -0
  113. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/settings/providers/tavily.py +0 -0
  114. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/settings/providers/vertexai_agent.py +0 -0
  115. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/settings/secret_str.py +0 -0
  116. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/startup_report.py +0 -0
  117. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/utils/__init__.py +0 -0
  118. {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev9}/unique_search_proxy_client/web/utils/url.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: unique-search-proxy
3
- Version: 2026.44.0.dev8
3
+ Version: 2026.44.0.dev9
4
4
  Summary: Web Search Proxy implementation
5
5
  Author: ThePhilAz
6
6
  Author-email: ThePhilAz <rami.azouz@philico.com>
@@ -21,7 +21,7 @@ Requires-Dist: google-genai>=1.73.0,<2
21
21
  Requires-Dist: google-auth>=2.43.0,<3
22
22
  Requires-Dist: jinja2>=3.1.6,<4
23
23
  Requires-Dist: unique-toolkit[monitoring]>=2026.44.0.dev9,<2026.44.0rc0
24
- Requires-Dist: unique-search-proxy-core>=2026.44.0.dev7,<2026.44.0rc0
24
+ Requires-Dist: unique-search-proxy-core>=2026.44.0.dev8,<2026.44.0rc0
25
25
  Requires-Dist: openai>=1.105.0,<3
26
26
  Requires-Python: >=3.12
27
27
  Description-Content-Type: text/markdown
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "unique-search-proxy"
3
- version = "2026.44.0.dev8"
3
+ version = "2026.44.0.dev9"
4
4
  description = "Web Search Proxy implementation"
5
5
  authors = [{ name = "ThePhilAz", email = "rami.azouz@philico.com" }]
6
6
  readme = "README.md"
@@ -23,7 +23,7 @@ dependencies = [
23
23
  "google-auth>=2.43.0,<3",
24
24
  "jinja2>=3.1.6,<4",
25
25
  "unique-toolkit[monitoring]>=2026.44.0.dev9,<2026.44.0rc0",
26
- "unique-search-proxy-core>=2026.44.0.dev7,<2026.44.0rc0",
26
+ "unique-search-proxy-core>=2026.44.0.dev8,<2026.44.0rc0",
27
27
  "openai>=1.105.0,<3",
28
28
  ]
29
29
 
@@ -10,6 +10,8 @@ from unique_search_proxy_core.crawlers.basic.processing.policy import (
10
10
  ContentTypeHandlerPolicy,
11
11
  )
12
12
  from unique_search_proxy_core.crawlers.basic.schema import BasicCrawlRequest
13
+ from unique_search_proxy_core.errors import ResponseTooLargeError
14
+ from unique_search_proxy_core.http_client import read_capped_body
13
15
  from unique_search_proxy_core.schema import (
14
16
  CrawlUrlResult,
15
17
  ProxyErrorCode,
@@ -18,7 +20,7 @@ from unique_search_proxy_core.url_safety import (
18
20
  CrawlTargetValidationError,
19
21
  ResolvedCrawlTarget,
20
22
  bypass_crawl_target,
21
- safe_pinned_httpx_get,
23
+ safe_pinned_httpx_stream,
22
24
  url_safety_settings,
23
25
  )
24
26
 
@@ -37,9 +39,15 @@ from unique_search_proxy_client.web.core.provider_response import (
37
39
  )
38
40
  from unique_search_proxy_client.web.core.url_safety.gate import AllowedCrawlTarget
39
41
  from unique_search_proxy_client.web.monitoring.metrics import record_crawl_blocked
42
+ from unique_search_proxy_client.web.settings.client import http_client_settings
40
43
 
41
44
  _LOGGER = logging.getLogger(__name__)
42
45
 
46
+ # Only encodings whose decoded size read_capped_body can bound in small steps;
47
+ # brotli and zstd reach far higher compression ratios.
48
+ _ACCEPT_ENCODING = "gzip, deflate"
49
+ _COMPRESSING_CONTENT_ENCODINGS = frozenset({"gzip", "deflate"})
50
+
43
51
 
44
52
  def _content_type_from_response(response: httpx.Response) -> str | None:
45
53
  header = response.headers.get("content-type")
@@ -48,6 +56,25 @@ def _content_type_from_response(response: httpx.Response) -> str | None:
48
56
  return str(header).split(";")[0].strip().lower() or None
49
57
 
50
58
 
59
+ def _unsupported_content_encoding(response: httpx.Response) -> str | None:
60
+ encodings = [
61
+ encoding.strip().lower()
62
+ for encoding in response.headers.get_list(
63
+ "content-encoding",
64
+ split_commas=True,
65
+ )
66
+ ]
67
+ for encoding in encodings:
68
+ if encoding not in {"", "identity", *_COMPRESSING_CONTENT_ENCODINGS}:
69
+ return encoding
70
+ # httpx decodes stacked layers in a single step, so a second compressing
71
+ # layer would inflate past read_capped_body before it can check the size.
72
+ compressing = [e for e in encodings if e in _COMPRESSING_CONTENT_ENCODINGS]
73
+ if len(compressing) > 1:
74
+ return ", ".join(compressing)
75
+ return None
76
+
77
+
51
78
  class BasicCrawlerService(BaseCrawler[BasicCrawlRequest]):
52
79
  """Fetch URLs over HTTP and return the response body plus content type."""
53
80
 
@@ -107,14 +134,38 @@ class BasicCrawlerService(BaseCrawler[BasicCrawlRequest]):
107
134
  ) -> CrawlUrlResult:
108
135
  async with semaphore:
109
136
  try:
110
- response = await safe_pinned_httpx_get(
137
+ async with safe_pinned_httpx_stream(
111
138
  client,
112
139
  resolved_target,
113
- headers={"User-Agent": random_user_agent()},
140
+ headers={
141
+ "User-Agent": random_user_agent(),
142
+ "Accept-Encoding": _ACCEPT_ENCODING,
143
+ },
114
144
  timeout=Timeout(timeout),
115
145
  max_redirect_hops=url_safety_settings.max_redirect_hops,
116
146
  enforce_url_safety=url_safety_settings.enabled,
117
- )
147
+ ) as response:
148
+ content_type = _content_type_from_response(response)
149
+ unsupported_encoding = _unsupported_content_encoding(response)
150
+ if unsupported_encoding is not None:
151
+ _LOGGER.warning(
152
+ "Basic crawl rejected content encoding %s",
153
+ unsupported_encoding,
154
+ )
155
+ return crawl_upstream_error(
156
+ display_url,
157
+ f"Unsupported content encoding: {unsupported_encoding}",
158
+ content_type=content_type,
159
+ status_code=response.status_code,
160
+ )
161
+ body = await read_capped_body(
162
+ response,
163
+ max_bytes=http_client_settings.max_response_bytes,
164
+ )
165
+ raw_body = body.decode(
166
+ response.encoding or "utf-8",
167
+ errors="replace",
168
+ )
118
169
  except CrawlTargetValidationError as exc:
119
170
  blocked = exc.blocked_targets[0]
120
171
  record_crawl_blocked(blocked.category)
@@ -123,6 +174,14 @@ class BasicCrawlerService(BaseCrawler[BasicCrawlRequest]):
123
174
  blocked.category,
124
175
  )
125
176
  return crawl_forbidden_target(display_url, blocked.reason)
177
+ except ResponseTooLargeError as exc:
178
+ _LOGGER.warning("Basic crawl response too large: %s", exc.message)
179
+ return crawl_upstream_error(
180
+ display_url,
181
+ exc.message,
182
+ content_type=None,
183
+ code=ProxyErrorCode.RESPONSE_TOO_LARGE.value,
184
+ )
126
185
  except httpx.TimeoutException as exc:
127
186
  _LOGGER.warning(
128
187
  "Basic crawl timed out error_type=%s",
@@ -147,8 +206,6 @@ class BasicCrawlerService(BaseCrawler[BasicCrawlRequest]):
147
206
  raw=transport_error_raw(exc),
148
207
  )
149
208
 
150
- content_type = _content_type_from_response(response)
151
- raw_body = response.text
152
209
  if response.is_error:
153
210
  _LOGGER.warning(
154
211
  "Basic crawl HTTP error http_status=%s",