unique-search-proxy-core 2026.32.0.dev0__tar.gz → 2026.32.0.dev2__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. {unique_search_proxy_core-2026.32.0.dev0 → unique_search_proxy_core-2026.32.0.dev2}/PKG-INFO +2 -2
  2. {unique_search_proxy_core-2026.32.0.dev0 → unique_search_proxy_core-2026.32.0.dev2}/pyproject.toml +2 -2
  3. {unique_search_proxy_core-2026.32.0.dev0 → unique_search_proxy_core-2026.32.0.dev2}/unique_search_proxy_core/schema.py +8 -0
  4. {unique_search_proxy_core-2026.32.0.dev0 → unique_search_proxy_core-2026.32.0.dev2}/unique_search_proxy_core/url_safety/service.py +31 -0
  5. {unique_search_proxy_core-2026.32.0.dev0 → unique_search_proxy_core-2026.32.0.dev2}/README.md +0 -0
  6. {unique_search_proxy_core-2026.32.0.dev0 → unique_search_proxy_core-2026.32.0.dev2}/unique_search_proxy_core/__init__.py +0 -0
  7. {unique_search_proxy_core-2026.32.0.dev0 → unique_search_proxy_core-2026.32.0.dev2}/unique_search_proxy_core/agent_engines/__init__.py +0 -0
  8. {unique_search_proxy_core-2026.32.0.dev0 → unique_search_proxy_core-2026.32.0.dev2}/unique_search_proxy_core/agent_engines/base.py +0 -0
  9. {unique_search_proxy_core-2026.32.0.dev0 → unique_search_proxy_core-2026.32.0.dev2}/unique_search_proxy_core/agent_engines/bing/__init__.py +0 -0
  10. {unique_search_proxy_core-2026.32.0.dev0 → unique_search_proxy_core-2026.32.0.dev2}/unique_search_proxy_core/agent_engines/bing/schema.py +0 -0
  11. {unique_search_proxy_core-2026.32.0.dev0 → unique_search_proxy_core-2026.32.0.dev2}/unique_search_proxy_core/agent_engines/config_types.py +0 -0
  12. {unique_search_proxy_core-2026.32.0.dev0 → unique_search_proxy_core-2026.32.0.dev2}/unique_search_proxy_core/agent_engines/output_schema.py +0 -0
  13. {unique_search_proxy_core-2026.32.0.dev0 → unique_search_proxy_core-2026.32.0.dev2}/unique_search_proxy_core/agent_engines/resolve.py +0 -0
  14. {unique_search_proxy_core-2026.32.0.dev0 → unique_search_proxy_core-2026.32.0.dev2}/unique_search_proxy_core/agent_engines/response_parsing.py +0 -0
  15. {unique_search_proxy_core-2026.32.0.dev0 → unique_search_proxy_core-2026.32.0.dev2}/unique_search_proxy_core/agent_engines/vertexai/__init__.py +0 -0
  16. {unique_search_proxy_core-2026.32.0.dev0 → unique_search_proxy_core-2026.32.0.dev2}/unique_search_proxy_core/agent_engines/vertexai/schema.py +0 -0
  17. {unique_search_proxy_core-2026.32.0.dev0 → unique_search_proxy_core-2026.32.0.dev2}/unique_search_proxy_core/context.py +0 -0
  18. {unique_search_proxy_core-2026.32.0.dev0 → unique_search_proxy_core-2026.32.0.dev2}/unique_search_proxy_core/crawlers/__init__.py +0 -0
  19. {unique_search_proxy_core-2026.32.0.dev0 → unique_search_proxy_core-2026.32.0.dev2}/unique_search_proxy_core/crawlers/base.py +0 -0
  20. {unique_search_proxy_core-2026.32.0.dev0 → unique_search_proxy_core-2026.32.0.dev2}/unique_search_proxy_core/crawlers/basic/__init__.py +0 -0
  21. {unique_search_proxy_core-2026.32.0.dev0 → unique_search_proxy_core-2026.32.0.dev2}/unique_search_proxy_core/crawlers/basic/content_types.py +0 -0
  22. {unique_search_proxy_core-2026.32.0.dev0 → unique_search_proxy_core-2026.32.0.dev2}/unique_search_proxy_core/crawlers/basic/processing/policy.py +0 -0
  23. {unique_search_proxy_core-2026.32.0.dev0 → unique_search_proxy_core-2026.32.0.dev2}/unique_search_proxy_core/crawlers/basic/schema.py +0 -0
  24. {unique_search_proxy_core-2026.32.0.dev0 → unique_search_proxy_core-2026.32.0.dev2}/unique_search_proxy_core/crawlers/config_types.py +0 -0
  25. {unique_search_proxy_core-2026.32.0.dev0 → unique_search_proxy_core-2026.32.0.dev2}/unique_search_proxy_core/crawlers/firecrawl/schema.py +0 -0
  26. {unique_search_proxy_core-2026.32.0.dev0 → unique_search_proxy_core-2026.32.0.dev2}/unique_search_proxy_core/crawlers/jina/schema.py +0 -0
  27. {unique_search_proxy_core-2026.32.0.dev0 → unique_search_proxy_core-2026.32.0.dev2}/unique_search_proxy_core/crawlers/tavily/schema.py +0 -0
  28. {unique_search_proxy_core-2026.32.0.dev0 → unique_search_proxy_core-2026.32.0.dev2}/unique_search_proxy_core/errors.py +0 -0
  29. {unique_search_proxy_core-2026.32.0.dev0 → unique_search_proxy_core-2026.32.0.dev2}/unique_search_proxy_core/logging.py +0 -0
  30. {unique_search_proxy_core-2026.32.0.dev0 → unique_search_proxy_core-2026.32.0.dev2}/unique_search_proxy_core/param_policy/__init__.py +0 -0
  31. {unique_search_proxy_core-2026.32.0.dev0 → unique_search_proxy_core-2026.32.0.dev2}/unique_search_proxy_core/param_policy/annotations.py +0 -0
  32. {unique_search_proxy_core-2026.32.0.dev0 → unique_search_proxy_core-2026.32.0.dev2}/unique_search_proxy_core/param_policy/derive.py +0 -0
  33. {unique_search_proxy_core-2026.32.0.dev0 → unique_search_proxy_core-2026.32.0.dev2}/unique_search_proxy_core/param_policy/exposable_param.py +0 -0
  34. {unique_search_proxy_core-2026.32.0.dev0 → unique_search_proxy_core-2026.32.0.dev2}/unique_search_proxy_core/param_policy/exposed_params.py +0 -0
  35. {unique_search_proxy_core-2026.32.0.dev0 → unique_search_proxy_core-2026.32.0.dev2}/unique_search_proxy_core/param_policy/request_base.py +0 -0
  36. {unique_search_proxy_core-2026.32.0.dev0 → unique_search_proxy_core-2026.32.0.dev2}/unique_search_proxy_core/providers/schema.py +0 -0
  37. {unique_search_proxy_core-2026.32.0.dev0 → unique_search_proxy_core-2026.32.0.dev2}/unique_search_proxy_core/search_engines/__init__.py +0 -0
  38. {unique_search_proxy_core-2026.32.0.dev0 → unique_search_proxy_core-2026.32.0.dev2}/unique_search_proxy_core/search_engines/base.py +0 -0
  39. {unique_search_proxy_core-2026.32.0.dev0 → unique_search_proxy_core-2026.32.0.dev2}/unique_search_proxy_core/search_engines/brave/__init__.py +0 -0
  40. {unique_search_proxy_core-2026.32.0.dev0 → unique_search_proxy_core-2026.32.0.dev2}/unique_search_proxy_core/search_engines/brave/schema.py +0 -0
  41. {unique_search_proxy_core-2026.32.0.dev0 → unique_search_proxy_core-2026.32.0.dev2}/unique_search_proxy_core/search_engines/config_types.py +0 -0
  42. {unique_search_proxy_core-2026.32.0.dev0 → unique_search_proxy_core-2026.32.0.dev2}/unique_search_proxy_core/search_engines/google/__init__.py +0 -0
  43. {unique_search_proxy_core-2026.32.0.dev0 → unique_search_proxy_core-2026.32.0.dev2}/unique_search_proxy_core/search_engines/google/schema.py +0 -0
  44. {unique_search_proxy_core-2026.32.0.dev0 → unique_search_proxy_core-2026.32.0.dev2}/unique_search_proxy_core/search_engines/perplexity/__init__.py +0 -0
  45. {unique_search_proxy_core-2026.32.0.dev0 → unique_search_proxy_core-2026.32.0.dev2}/unique_search_proxy_core/search_engines/perplexity/schema.py +0 -0
  46. {unique_search_proxy_core-2026.32.0.dev0 → unique_search_proxy_core-2026.32.0.dev2}/unique_search_proxy_core/url_safety/__init__.py +0 -0
  47. {unique_search_proxy_core-2026.32.0.dev0 → unique_search_proxy_core-2026.32.0.dev2}/unique_search_proxy_core/url_safety/dns.py +0 -0
  48. {unique_search_proxy_core-2026.32.0.dev0 → unique_search_proxy_core-2026.32.0.dev2}/unique_search_proxy_core/url_safety/egress.py +0 -0
  49. {unique_search_proxy_core-2026.32.0.dev0 → unique_search_proxy_core-2026.32.0.dev2}/unique_search_proxy_core/url_safety/models.py +0 -0
  50. {unique_search_proxy_core-2026.32.0.dev0 → unique_search_proxy_core-2026.32.0.dev2}/unique_search_proxy_core/url_safety/netloc.py +0 -0
  51. {unique_search_proxy_core-2026.32.0.dev0 → unique_search_proxy_core-2026.32.0.dev2}/unique_search_proxy_core/url_safety/policy.py +0 -0
  52. {unique_search_proxy_core-2026.32.0.dev0 → unique_search_proxy_core-2026.32.0.dev2}/unique_search_proxy_core/url_safety/redirect.py +0 -0
  53. {unique_search_proxy_core-2026.32.0.dev0 → unique_search_proxy_core-2026.32.0.dev2}/unique_search_proxy_core/url_safety/resolver.py +0 -0
  54. {unique_search_proxy_core-2026.32.0.dev0 → unique_search_proxy_core-2026.32.0.dev2}/unique_search_proxy_core/url_safety/settings.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: unique-search-proxy-core
3
- Version: 2026.32.0.dev0
3
+ Version: 2026.32.0.dev2
4
4
  Summary: Shared Pydantic types for the Unique Search Proxy API
5
5
  Author: ThePhilAz
6
6
  Author-email: ThePhilAz <rami.azouz@philico.com>
@@ -8,7 +8,7 @@ Requires-Dist: httpx>=0.28.0,<0.29
8
8
  Requires-Dist: pydantic>=2.12.5,<3.0.0
9
9
  Requires-Dist: pydantic-settings>=2.10.1,<3
10
10
  Requires-Dist: pyhumps>=3.8.0,<4
11
- Requires-Dist: unique-toolkit[monitoring]>=2026.32.0.dev0,<2026.32.0rc0
11
+ Requires-Dist: unique-toolkit[monitoring]>=2026.32.0.dev3,<2026.32.0rc0
12
12
  Requires-Python: >=3.12
13
13
  Description-Content-Type: text/markdown
14
14
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "unique-search-proxy-core"
3
- version = "2026.32.0.dev0"
3
+ version = "2026.32.0.dev2"
4
4
  description = "Shared Pydantic types for the Unique Search Proxy API"
5
5
  authors = [{ name = "ThePhilAz", email = "rami.azouz@philico.com" }]
6
6
  readme = "README.md"
@@ -10,7 +10,7 @@ dependencies = [
10
10
  "pydantic>=2.12.5,<3.0.0",
11
11
  "pydantic-settings>=2.10.1,<3",
12
12
  "pyhumps>=3.8.0,<4",
13
- "unique-toolkit[monitoring]>=2026.32.0.dev0,<2026.32.0rc0",
13
+ "unique-toolkit[monitoring]>=2026.32.0.dev3,<2026.32.0rc0",
14
14
 
15
15
  ]
16
16
 
@@ -142,6 +142,14 @@ class PerUrlError(BaseModel):
142
142
 
143
143
  code: str
144
144
  message: str
145
+ status_code: int | None = Field(
146
+ default=None,
147
+ description=(
148
+ "Upstream HTTP status code for this URL when the failure was an HTTP "
149
+ "error (e.g. 403, 404, 500 from the Basic crawler); null when no HTTP "
150
+ "response was received (timeouts, transport, processing, blocked targets)"
151
+ ),
152
+ )
145
153
 
146
154
 
147
155
  class CrawlUrlResult(BaseModel):
@@ -1,10 +1,12 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import logging
3
4
  from ipaddress import ip_address
4
5
  from urllib.parse import urlsplit
5
6
 
6
7
  from unique_search_proxy_core.url_safety import dns, redirect, resolver
7
8
  from unique_search_proxy_core.url_safety.models import (
9
+ BlockedCrawlTarget,
8
10
  CrawlTargetValidationError,
9
11
  ResolvedCrawlTarget,
10
12
  UrlSafetyOutcome,
@@ -13,6 +15,21 @@ from unique_search_proxy_core.url_safety.models import (
13
15
  from unique_search_proxy_core.url_safety.policy import validate_target_cheap
14
16
  from unique_search_proxy_core.url_safety.settings import url_safety_settings
15
17
 
18
+ _LOGGER = logging.getLogger(__name__)
19
+
20
+
21
+ def _safe_hostname(url: str) -> str | None:
22
+ """Extract the hostname from a URL without ever raising.
23
+
24
+ ``urlsplit(...).hostname`` raises ``ValueError`` for malformed IPv6 hosts
25
+ (e.g. ``http://[::1``). This must never happen on the fail-closed path,
26
+ where a single bad URL would otherwise abort the whole batch.
27
+ """
28
+ try:
29
+ return urlsplit(url).hostname
30
+ except ValueError:
31
+ return None
32
+
16
33
 
17
34
  class UrlSafetyService:
18
35
  @staticmethod
@@ -37,6 +54,20 @@ class UrlSafetyService:
37
54
  except CrawlTargetValidationError as exc:
38
55
  blocked = exc.blocked_targets[0]
39
56
  outcomes.append(UrlSafetyOutcome(url=url, blocked=blocked))
57
+ except Exception:
58
+ # Fail closed: an unexpected error while validating a single URL
59
+ # must block only that URL, never fail the whole batch.
60
+ _LOGGER.exception("URL safety validation failed unexpectedly")
61
+ outcomes.append(
62
+ UrlSafetyOutcome(
63
+ url=url,
64
+ blocked=BlockedCrawlTarget(
65
+ hostname=_safe_hostname(working_url),
66
+ category="validation_error",
67
+ reason="URL safety validation failed unexpectedly",
68
+ ),
69
+ )
70
+ )
40
71
  return outcomes
41
72
 
42
73
  @staticmethod