unique-search-proxy 2026.44.0.dev8__tar.gz → 2026.44.0.dev10__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.
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/PKG-INFO +2 -2
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/pyproject.toml +2 -2
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/core/crawlers/basic/service.py +77 -6
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/helm/registry.py +4 -0
- unique_search_proxy-2026.44.0.dev10/unique_search_proxy_client/web/settings/request_limits.py +60 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/README.md +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/__init__.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/__init__.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/api/__init__.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/api/health.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/api/v1/__init__.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/api/v1/agent_search.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/api/v1/configuration.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/api/v1/context_headers.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/api/v1/crawl.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/api/v1/openapi_examples.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/api/v1/search.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/app.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/context.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/core/__init__.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/core/agent_engines/__init__.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/core/agent_engines/bing/cleanup.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/core/agent_engines/bing/client.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/core/agent_engines/bing/runner.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/core/agent_engines/bing/service.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/core/agent_engines/factory.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/core/agent_engines/service_base.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/core/agent_engines/structured_output.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/core/agent_engines/vertexai/client.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/core/agent_engines/vertexai/gemini.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/core/agent_engines/vertexai/redirects.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/core/agent_engines/vertexai/service.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/core/client/__init__.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/core/client/azure_transport.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/core/client/credentials.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/core/client/service.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/core/crawlers/__init__.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/core/crawlers/basic/__init__.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/core/crawlers/basic/processing/__init__.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/core/crawlers/basic/processing/errors.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/core/crawlers/basic/processing/html_markdown.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/core/crawlers/basic/processing/processors/__init__.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/core/crawlers/basic/processing/processors/html.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/core/crawlers/basic/processing/processors/pdf.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/core/crawlers/basic/processing/processors/plain_text.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/core/crawlers/basic/processing/registry.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/core/crawlers/basic/settings.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/core/crawlers/basic/user_agent.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/core/crawlers/factory.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/core/crawlers/firecrawl/polling.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/core/crawlers/firecrawl/request_body.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/core/crawlers/firecrawl/service.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/core/crawlers/jina/request_body.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/core/crawlers/jina/service.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/core/crawlers/pinned_egress.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/core/crawlers/tavily/request_body.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/core/crawlers/tavily/service.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/core/provider_response.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/core/providers.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/core/registry.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/core/search_engines/__init__.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/core/search_engines/brave/__init__.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/core/search_engines/brave/pagination.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/core/search_engines/brave/query_params.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/core/search_engines/brave/service.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/core/search_engines/custom_api/__init__.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/core/search_engines/custom_api/service.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/core/search_engines/descriptor.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/core/search_engines/factory.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/core/search_engines/google/__init__.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/core/search_engines/google/pagination.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/core/search_engines/google/query_params.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/core/search_engines/google/service.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/core/search_engines/pagination.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/core/search_engines/perplexity/__init__.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/core/search_engines/perplexity/request_body.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/core/search_engines/perplexity/service.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/core/search_engines/service_base.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/core/url_safety/__init__.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/core/url_safety/gate.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/error_handlers.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/helm/__init__.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/helm/generator/__init__.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/helm/generator/introspect.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/helm/generator/schema.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/helm/generator/template.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/helm/generator/templates/provider_values_yaml.j2 +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/helm/generator/values_yaml.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/helm/metadata.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/helm/url_safety_registration.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/logging_config.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/middleware/context.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/monitoring/__init__.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/monitoring/metrics.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/monitoring/setup.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/presets/__init__.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/presets/common.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/presets/crawl.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/presets/search.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/presets/types.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/settings/__init__.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/settings/app.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/settings/base.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/settings/client.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/settings/monitoring.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/settings/providers/__init__.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/settings/providers/base.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/settings/providers/bing_agent.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/settings/providers/brave.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/settings/providers/firecrawl.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/settings/providers/google.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/settings/providers/jina.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/settings/providers/perplexity.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/settings/providers/tavily.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/settings/providers/vertexai_agent.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/settings/secret_str.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/startup_report.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/unique_search_proxy_client/web/utils/__init__.py +0 -0
- {unique_search_proxy-2026.44.0.dev8 → unique_search_proxy-2026.44.0.dev10}/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.
|
|
3
|
+
Version: 2026.44.0.dev10
|
|
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.
|
|
24
|
+
Requires-Dist: unique-search-proxy-core>=2026.44.0.dev9,<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.
|
|
3
|
+
version = "2026.44.0.dev10"
|
|
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.
|
|
26
|
+
"unique-search-proxy-core>=2026.44.0.dev9,<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 ByteBudget, 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
|
-
|
|
23
|
+
safe_pinned_httpx_stream,
|
|
22
24
|
url_safety_settings,
|
|
23
25
|
)
|
|
24
26
|
|
|
@@ -37,9 +39,18 @@ 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
|
|
43
|
+
from unique_search_proxy_client.web.settings.request_limits import (
|
|
44
|
+
request_limits_settings,
|
|
45
|
+
)
|
|
40
46
|
|
|
41
47
|
_LOGGER = logging.getLogger(__name__)
|
|
42
48
|
|
|
49
|
+
# Only encodings whose decoded size read_capped_body can bound in small steps;
|
|
50
|
+
# brotli and zstd reach far higher compression ratios.
|
|
51
|
+
_ACCEPT_ENCODING = "gzip, deflate"
|
|
52
|
+
_COMPRESSING_CONTENT_ENCODINGS = frozenset({"gzip", "deflate"})
|
|
53
|
+
|
|
43
54
|
|
|
44
55
|
def _content_type_from_response(response: httpx.Response) -> str | None:
|
|
45
56
|
header = response.headers.get("content-type")
|
|
@@ -48,6 +59,25 @@ def _content_type_from_response(response: httpx.Response) -> str | None:
|
|
|
48
59
|
return str(header).split(";")[0].strip().lower() or None
|
|
49
60
|
|
|
50
61
|
|
|
62
|
+
def _unsupported_content_encoding(response: httpx.Response) -> str | None:
|
|
63
|
+
encodings = [
|
|
64
|
+
encoding.strip().lower()
|
|
65
|
+
for encoding in response.headers.get_list(
|
|
66
|
+
"content-encoding",
|
|
67
|
+
split_commas=True,
|
|
68
|
+
)
|
|
69
|
+
]
|
|
70
|
+
for encoding in encodings:
|
|
71
|
+
if encoding not in {"", "identity", *_COMPRESSING_CONTENT_ENCODINGS}:
|
|
72
|
+
return encoding
|
|
73
|
+
# httpx decodes stacked layers in a single step, so a second compressing
|
|
74
|
+
# layer would inflate past read_capped_body before it can check the size.
|
|
75
|
+
compressing = [e for e in encodings if e in _COMPRESSING_CONTENT_ENCODINGS]
|
|
76
|
+
if len(compressing) > 1:
|
|
77
|
+
return ", ".join(compressing)
|
|
78
|
+
return None
|
|
79
|
+
|
|
80
|
+
|
|
51
81
|
class BasicCrawlerService(BaseCrawler[BasicCrawlRequest]):
|
|
52
82
|
"""Fetch URLs over HTTP and return the response body plus content type."""
|
|
53
83
|
|
|
@@ -79,6 +109,7 @@ class BasicCrawlerService(BaseCrawler[BasicCrawlRequest]):
|
|
|
79
109
|
|
|
80
110
|
timeout = request.timeout
|
|
81
111
|
semaphore = asyncio.Semaphore(request.max_concurrent_requests)
|
|
112
|
+
budget = ByteBudget(request_limits_settings.max_crawl_response_bytes)
|
|
82
113
|
return list(
|
|
83
114
|
await asyncio.gather(
|
|
84
115
|
*[
|
|
@@ -88,6 +119,7 @@ class BasicCrawlerService(BaseCrawler[BasicCrawlRequest]):
|
|
|
88
119
|
resolved_target=allowed_target.resolved,
|
|
89
120
|
timeout=timeout,
|
|
90
121
|
semaphore=semaphore,
|
|
122
|
+
budget=budget,
|
|
91
123
|
content_type_handlers=request.content_types.to_handlers(),
|
|
92
124
|
)
|
|
93
125
|
for allowed_target in allowed_targets
|
|
@@ -103,18 +135,51 @@ class BasicCrawlerService(BaseCrawler[BasicCrawlRequest]):
|
|
|
103
135
|
resolved_target: ResolvedCrawlTarget,
|
|
104
136
|
timeout: int,
|
|
105
137
|
semaphore: asyncio.Semaphore,
|
|
138
|
+
budget: ByteBudget,
|
|
106
139
|
content_type_handlers: dict[str, ContentTypeHandlerPolicy],
|
|
107
140
|
) -> CrawlUrlResult:
|
|
108
141
|
async with semaphore:
|
|
142
|
+
if budget.exhausted:
|
|
143
|
+
return crawl_upstream_error(
|
|
144
|
+
display_url,
|
|
145
|
+
"Crawl response budget exhausted before this URL was fetched",
|
|
146
|
+
content_type=None,
|
|
147
|
+
code=ProxyErrorCode.RESPONSE_TOO_LARGE.value,
|
|
148
|
+
)
|
|
109
149
|
try:
|
|
110
|
-
|
|
150
|
+
async with safe_pinned_httpx_stream(
|
|
111
151
|
client,
|
|
112
152
|
resolved_target,
|
|
113
|
-
headers={
|
|
153
|
+
headers={
|
|
154
|
+
"User-Agent": random_user_agent(),
|
|
155
|
+
"Accept-Encoding": _ACCEPT_ENCODING,
|
|
156
|
+
},
|
|
114
157
|
timeout=Timeout(timeout),
|
|
115
158
|
max_redirect_hops=url_safety_settings.max_redirect_hops,
|
|
116
159
|
enforce_url_safety=url_safety_settings.enabled,
|
|
117
|
-
)
|
|
160
|
+
) as response:
|
|
161
|
+
content_type = _content_type_from_response(response)
|
|
162
|
+
unsupported_encoding = _unsupported_content_encoding(response)
|
|
163
|
+
if unsupported_encoding is not None:
|
|
164
|
+
_LOGGER.warning(
|
|
165
|
+
"Basic crawl rejected content encoding %s",
|
|
166
|
+
unsupported_encoding,
|
|
167
|
+
)
|
|
168
|
+
return crawl_upstream_error(
|
|
169
|
+
display_url,
|
|
170
|
+
f"Unsupported content encoding: {unsupported_encoding}",
|
|
171
|
+
content_type=content_type,
|
|
172
|
+
status_code=response.status_code,
|
|
173
|
+
)
|
|
174
|
+
body = await read_capped_body(
|
|
175
|
+
response,
|
|
176
|
+
max_bytes=http_client_settings.max_response_bytes,
|
|
177
|
+
budget=budget,
|
|
178
|
+
)
|
|
179
|
+
raw_body = body.decode(
|
|
180
|
+
response.encoding or "utf-8",
|
|
181
|
+
errors="replace",
|
|
182
|
+
)
|
|
118
183
|
except CrawlTargetValidationError as exc:
|
|
119
184
|
blocked = exc.blocked_targets[0]
|
|
120
185
|
record_crawl_blocked(blocked.category)
|
|
@@ -123,6 +188,14 @@ class BasicCrawlerService(BaseCrawler[BasicCrawlRequest]):
|
|
|
123
188
|
blocked.category,
|
|
124
189
|
)
|
|
125
190
|
return crawl_forbidden_target(display_url, blocked.reason)
|
|
191
|
+
except ResponseTooLargeError as exc:
|
|
192
|
+
_LOGGER.warning("Basic crawl response too large: %s", exc.message)
|
|
193
|
+
return crawl_upstream_error(
|
|
194
|
+
display_url,
|
|
195
|
+
exc.message,
|
|
196
|
+
content_type=None,
|
|
197
|
+
code=ProxyErrorCode.RESPONSE_TOO_LARGE.value,
|
|
198
|
+
)
|
|
126
199
|
except httpx.TimeoutException as exc:
|
|
127
200
|
_LOGGER.warning(
|
|
128
201
|
"Basic crawl timed out error_type=%s",
|
|
@@ -147,8 +220,6 @@ class BasicCrawlerService(BaseCrawler[BasicCrawlRequest]):
|
|
|
147
220
|
raw=transport_error_raw(exc),
|
|
148
221
|
)
|
|
149
222
|
|
|
150
|
-
content_type = _content_type_from_response(response)
|
|
151
|
-
raw_body = response.text
|
|
152
223
|
if response.is_error:
|
|
153
224
|
_LOGGER.warning(
|
|
154
225
|
"Basic crawl HTTP error http_status=%s",
|
|
@@ -31,6 +31,9 @@ from unique_search_proxy_client.web.settings.providers import (
|
|
|
31
31
|
tavily_crawl_credentials,
|
|
32
32
|
vertexai_agent_credentials,
|
|
33
33
|
)
|
|
34
|
+
from unique_search_proxy_client.web.settings.request_limits import (
|
|
35
|
+
request_limits_settings,
|
|
36
|
+
)
|
|
34
37
|
|
|
35
38
|
|
|
36
39
|
@dataclass(frozen=True)
|
|
@@ -61,6 +64,7 @@ _REGISTERED_INSTANCES: tuple[BaseSettings, ...] = (
|
|
|
61
64
|
jina_crawl_credentials,
|
|
62
65
|
firecrawl_crawl_credentials,
|
|
63
66
|
http_client_settings,
|
|
67
|
+
request_limits_settings,
|
|
64
68
|
prometheus_settings,
|
|
65
69
|
)
|
|
66
70
|
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""Server-side ceilings that bound what a single proxy request may consume."""
|
|
2
|
+
|
|
3
|
+
from pydantic import Field
|
|
4
|
+
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
5
|
+
|
|
6
|
+
from unique_search_proxy_client.web.helm.metadata import helm_settings
|
|
7
|
+
from unique_search_proxy_client.web.settings.base import get_settings
|
|
8
|
+
|
|
9
|
+
REQUEST_LIMITS_ENV_PREFIX = "REQUEST_LIMITS_"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@helm_settings(
|
|
13
|
+
title="Request Limits",
|
|
14
|
+
helm_key="requestLimits",
|
|
15
|
+
kind="internal",
|
|
16
|
+
gated=False,
|
|
17
|
+
egress=None,
|
|
18
|
+
env_prefix=REQUEST_LIMITS_ENV_PREFIX,
|
|
19
|
+
sections={
|
|
20
|
+
"crawl": ["max_crawl_response_kib"],
|
|
21
|
+
},
|
|
22
|
+
)
|
|
23
|
+
class RequestLimitsSettings(BaseSettings):
|
|
24
|
+
"""Ceilings callers cannot raise, sized against the pod memory limit.
|
|
25
|
+
|
|
26
|
+
Byte sizes are in KiB: Helm renders numbers >= 1e6 in scientific notation,
|
|
27
|
+
which pydantic cannot parse back into an int.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
model_config = SettingsConfigDict(
|
|
31
|
+
env_prefix=REQUEST_LIMITS_ENV_PREFIX,
|
|
32
|
+
extra="ignore",
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
max_crawl_response_kib: int = Field(
|
|
36
|
+
default=10 * 1024,
|
|
37
|
+
ge=1,
|
|
38
|
+
description=(
|
|
39
|
+
"Combined decoded size, in KiB, of all page bodies one crawl request "
|
|
40
|
+
"may keep. URLs read after the budget is spent fail individually."
|
|
41
|
+
),
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def max_crawl_response_bytes(self) -> int:
|
|
46
|
+
return self.max_crawl_response_kib * 1024
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def get_request_limits_settings() -> RequestLimitsSettings:
|
|
50
|
+
return get_settings(RequestLimitsSettings, env_prefix=REQUEST_LIMITS_ENV_PREFIX)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
request_limits_settings: RequestLimitsSettings = get_request_limits_settings()
|
|
54
|
+
|
|
55
|
+
__all__ = [
|
|
56
|
+
"REQUEST_LIMITS_ENV_PREFIX",
|
|
57
|
+
"RequestLimitsSettings",
|
|
58
|
+
"get_request_limits_settings",
|
|
59
|
+
"request_limits_settings",
|
|
60
|
+
]
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|