nonecap 0.6.0__tar.gz → 0.6.2__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.
- {nonecap-0.6.0 → nonecap-0.6.2}/PKG-INFO +1 -1
- {nonecap-0.6.0 → nonecap-0.6.2}/src/nonecap/_types.py +5 -0
- nonecap-0.6.2/src/nonecap/_version.py +1 -0
- {nonecap-0.6.0 → nonecap-0.6.2}/tests/test_client.py +60 -0
- {nonecap-0.6.0 → nonecap-0.6.2}/tests/typing_assertions.py +12 -0
- nonecap-0.6.0/src/nonecap/_version.py +0 -1
- {nonecap-0.6.0 → nonecap-0.6.2}/.github/workflows/ci.yml +0 -0
- {nonecap-0.6.0 → nonecap-0.6.2}/.gitignore +0 -0
- {nonecap-0.6.0 → nonecap-0.6.2}/LICENSE +0 -0
- {nonecap-0.6.0 → nonecap-0.6.2}/README.md +0 -0
- {nonecap-0.6.0 → nonecap-0.6.2}/pyproject.toml +0 -0
- {nonecap-0.6.0 → nonecap-0.6.2}/src/nonecap/__init__.py +0 -0
- {nonecap-0.6.0 → nonecap-0.6.2}/src/nonecap/_client.py +0 -0
- {nonecap-0.6.0 → nonecap-0.6.2}/src/nonecap/_errors.py +0 -0
- {nonecap-0.6.0 → nonecap-0.6.2}/src/nonecap/py.typed +0 -0
- {nonecap-0.6.0 → nonecap-0.6.2}/tests/__init__.py +0 -0
- {nonecap-0.6.0 → nonecap-0.6.2}/tests/conftest.py +0 -0
- {nonecap-0.6.0 → nonecap-0.6.2}/tests/test_async_client.py +0 -0
- {nonecap-0.6.0 → nonecap-0.6.2}/tests/test_feedback.py +0 -0
|
@@ -55,6 +55,11 @@ SolveErrorReason = Literal[
|
|
|
55
55
|
"proxy_rejected",
|
|
56
56
|
"proxy_tls",
|
|
57
57
|
"proxy_stalled",
|
|
58
|
+
"proxy_egress_blocked",
|
|
59
|
+
"target_egress_blocked",
|
|
60
|
+
"profile_engine_unavailable",
|
|
61
|
+
"type_not_served",
|
|
62
|
+
"browser_lane_capped",
|
|
58
63
|
]
|
|
59
64
|
"""A typed sub-reason within :data:`SolveErrorCode`, set when the solver knows more
|
|
60
65
|
than the code says."""
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.6.2"
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from typing import get_args
|
|
4
|
+
|
|
3
5
|
import httpx
|
|
4
6
|
import pytest
|
|
5
7
|
|
|
@@ -17,6 +19,7 @@ from nonecap import (
|
|
|
17
19
|
RateLimitError,
|
|
18
20
|
ServiceUnavailableError,
|
|
19
21
|
SitekeyRateLimitedError,
|
|
22
|
+
SolveErrorReason,
|
|
20
23
|
SolveFailedError,
|
|
21
24
|
SolveTimeoutError,
|
|
22
25
|
UnsupportedMediaTypeError,
|
|
@@ -254,6 +257,63 @@ class TestErrorMapping:
|
|
|
254
257
|
assert info.value.reason == "proxy_rejected"
|
|
255
258
|
assert info.value.retryable is False
|
|
256
259
|
|
|
260
|
+
@pytest.mark.parametrize(
|
|
261
|
+
("code", "reason"),
|
|
262
|
+
[
|
|
263
|
+
("proxy_error", "proxy_egress_blocked"),
|
|
264
|
+
("target_unreachable", "target_egress_blocked"),
|
|
265
|
+
],
|
|
266
|
+
)
|
|
267
|
+
def test_egress_blocked_solves_are_typed_and_not_retryable(
|
|
268
|
+
self, code: str, reason: str
|
|
269
|
+
) -> None:
|
|
270
|
+
assert reason in get_args(SolveErrorReason)
|
|
271
|
+
failed = solve_payload(
|
|
272
|
+
status="failed",
|
|
273
|
+
error={
|
|
274
|
+
"code": code,
|
|
275
|
+
"message": "points at a private or internal network address",
|
|
276
|
+
"reason": reason,
|
|
277
|
+
"retryable": False,
|
|
278
|
+
"docs_url": "https://nonecap.com/api-reference#errors",
|
|
279
|
+
},
|
|
280
|
+
)
|
|
281
|
+
nc = client_for(Script((200, failed)))
|
|
282
|
+
with pytest.raises(SolveFailedError) as info:
|
|
283
|
+
nc.solve(type="hcaptcha", sitekey="sk", url="https://example.com")
|
|
284
|
+
assert info.value.solve_code == code
|
|
285
|
+
assert info.value.reason == reason
|
|
286
|
+
assert info.value.retryable is False
|
|
287
|
+
|
|
288
|
+
@pytest.mark.parametrize(
|
|
289
|
+
("code", "reason", "retryable"),
|
|
290
|
+
[
|
|
291
|
+
("capacity_exhausted", "profile_engine_unavailable", True),
|
|
292
|
+
("capacity_exhausted", "browser_lane_capped", True),
|
|
293
|
+
("internal_error", "type_not_served", False),
|
|
294
|
+
],
|
|
295
|
+
)
|
|
296
|
+
def test_uncharged_capacity_and_type_reasons_are_typed(
|
|
297
|
+
self, code: str, reason: str, retryable: bool
|
|
298
|
+
) -> None:
|
|
299
|
+
assert reason in get_args(SolveErrorReason)
|
|
300
|
+
failed = solve_payload(
|
|
301
|
+
status="failed",
|
|
302
|
+
error={
|
|
303
|
+
"code": code,
|
|
304
|
+
"message": "not charged",
|
|
305
|
+
"reason": reason,
|
|
306
|
+
"retryable": retryable,
|
|
307
|
+
"docs_url": "https://nonecap.com/api-reference#errors",
|
|
308
|
+
},
|
|
309
|
+
)
|
|
310
|
+
nc = client_for(Script((200, failed)))
|
|
311
|
+
with pytest.raises(SolveFailedError) as info:
|
|
312
|
+
nc.solve(type="hcaptcha", sitekey="sk", url="https://example.com")
|
|
313
|
+
assert info.value.solve_code == code
|
|
314
|
+
assert info.value.reason == reason
|
|
315
|
+
assert info.value.retryable is retryable
|
|
316
|
+
|
|
257
317
|
def test_older_server_error_object_still_parses(self) -> None:
|
|
258
318
|
failed = solve_payload(status="failed", error={"code": "vision_error", "message": "x"})
|
|
259
319
|
nc = client_for(Script((200, failed)))
|
|
@@ -20,6 +20,7 @@ from nonecap import (
|
|
|
20
20
|
FeedbackReport,
|
|
21
21
|
NoneCap,
|
|
22
22
|
Solve,
|
|
23
|
+
SolveErrorReason,
|
|
23
24
|
SolveHandle,
|
|
24
25
|
SolveTimeoutError,
|
|
25
26
|
)
|
|
@@ -84,6 +85,17 @@ def negative_cases() -> None:
|
|
|
84
85
|
nc.solve(type="recaptcha", sitekey="s", url="u") # type: ignore[call-overload]
|
|
85
86
|
|
|
86
87
|
|
|
88
|
+
def egress_blocked_reasons() -> None:
|
|
89
|
+
# The egress guard's reasons are part of the typed vocabulary.
|
|
90
|
+
_r1: SolveErrorReason = "proxy_egress_blocked"
|
|
91
|
+
_r2: SolveErrorReason = "target_egress_blocked"
|
|
92
|
+
_r3: SolveErrorReason = "profile_engine_unavailable"
|
|
93
|
+
_r4: SolveErrorReason = "type_not_served"
|
|
94
|
+
_r5: SolveErrorReason = "browser_lane_capped"
|
|
95
|
+
# A reason the API never defined must not type-check.
|
|
96
|
+
_bad: SolveErrorReason = "proxy_egress_blockd" # type: ignore[assignment]
|
|
97
|
+
|
|
98
|
+
|
|
87
99
|
def timeout_error_fields(err: SolveTimeoutError) -> None:
|
|
88
100
|
# SolveTimeoutError exposes the solve it timed out on.
|
|
89
101
|
_sid: Optional[str] = err.solve_id
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.6.0"
|
|
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
|