nonecap 0.3.0__tar.gz → 0.4.0__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.3.0 → nonecap-0.4.0}/PKG-INFO +4 -3
- {nonecap-0.3.0 → nonecap-0.4.0}/README.md +3 -2
- {nonecap-0.3.0 → nonecap-0.4.0}/src/nonecap/_client.py +8 -8
- {nonecap-0.3.0 → nonecap-0.4.0}/src/nonecap/_types.py +11 -8
- nonecap-0.4.0/src/nonecap/_version.py +1 -0
- {nonecap-0.3.0 → nonecap-0.4.0}/tests/test_feedback.py +16 -15
- {nonecap-0.3.0 → nonecap-0.4.0}/tests/typing_assertions.py +2 -2
- nonecap-0.3.0/src/nonecap/_version.py +0 -1
- {nonecap-0.3.0 → nonecap-0.4.0}/.github/workflows/ci.yml +0 -0
- {nonecap-0.3.0 → nonecap-0.4.0}/.gitignore +0 -0
- {nonecap-0.3.0 → nonecap-0.4.0}/LICENSE +0 -0
- {nonecap-0.3.0 → nonecap-0.4.0}/pyproject.toml +0 -0
- {nonecap-0.3.0 → nonecap-0.4.0}/src/nonecap/__init__.py +0 -0
- {nonecap-0.3.0 → nonecap-0.4.0}/src/nonecap/_errors.py +0 -0
- {nonecap-0.3.0 → nonecap-0.4.0}/src/nonecap/py.typed +0 -0
- {nonecap-0.3.0 → nonecap-0.4.0}/tests/__init__.py +0 -0
- {nonecap-0.3.0 → nonecap-0.4.0}/tests/conftest.py +0 -0
- {nonecap-0.3.0 → nonecap-0.4.0}/tests/test_async_client.py +0 -0
- {nonecap-0.3.0 → nonecap-0.4.0}/tests/test_client.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nonecap
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Official Python client for the NoneCap hCaptcha solving API.
|
|
5
5
|
Project-URL: Homepage, https://nonecap.com
|
|
6
6
|
Project-URL: Documentation, https://nonecap.com/api-reference
|
|
@@ -210,7 +210,8 @@ ok = submit_to_the_site_you_are_automating(solve.token)
|
|
|
210
210
|
nc.feedback.report(
|
|
211
211
|
solve.id,
|
|
212
212
|
outcome="accepted" if ok else "rejected",
|
|
213
|
-
reason=None if ok else "session invalidated", # optional,
|
|
213
|
+
reason=None if ok else "session invalidated", # optional, the code your target returned
|
|
214
|
+
context=None if ok else "3rd retry, rotating residential pool", # optional, anything you think would help us diagnose it
|
|
214
215
|
)
|
|
215
216
|
```
|
|
216
217
|
|
|
@@ -219,7 +220,7 @@ At volume, buffer the verdicts and flush them in one call. Reports over 500 are
|
|
|
219
220
|
```python
|
|
220
221
|
batch = nc.feedback.report_many([
|
|
221
222
|
{"solve_id": "solve_01J...", "outcome": "accepted"},
|
|
222
|
-
{"solve_id": "solve_01J...", "outcome": "rejected", "reason": "..."},
|
|
223
|
+
{"solve_id": "solve_01J...", "outcome": "rejected", "reason": "...", "context": "..."},
|
|
223
224
|
])
|
|
224
225
|
|
|
225
226
|
# Items resolve independently, so the call succeeds even when some are
|
|
@@ -179,7 +179,8 @@ ok = submit_to_the_site_you_are_automating(solve.token)
|
|
|
179
179
|
nc.feedback.report(
|
|
180
180
|
solve.id,
|
|
181
181
|
outcome="accepted" if ok else "rejected",
|
|
182
|
-
reason=None if ok else "session invalidated", # optional,
|
|
182
|
+
reason=None if ok else "session invalidated", # optional, the code your target returned
|
|
183
|
+
context=None if ok else "3rd retry, rotating residential pool", # optional, anything you think would help us diagnose it
|
|
183
184
|
)
|
|
184
185
|
```
|
|
185
186
|
|
|
@@ -188,7 +189,7 @@ At volume, buffer the verdicts and flush them in one call. Reports over 500 are
|
|
|
188
189
|
```python
|
|
189
190
|
batch = nc.feedback.report_many([
|
|
190
191
|
{"solve_id": "solve_01J...", "outcome": "accepted"},
|
|
191
|
-
{"solve_id": "solve_01J...", "outcome": "rejected", "reason": "..."},
|
|
192
|
+
{"solve_id": "solve_01J...", "outcome": "rejected", "reason": "...", "context": "..."},
|
|
192
193
|
])
|
|
193
194
|
|
|
194
195
|
# Items resolve independently, so the call succeeds even when some are
|
|
@@ -133,17 +133,17 @@ def _list_params(
|
|
|
133
133
|
|
|
134
134
|
def _feedback_fields(
|
|
135
135
|
outcome: FeedbackOutcome,
|
|
136
|
-
estado: Optional[bool],
|
|
137
136
|
reason: Optional[str],
|
|
137
|
+
context: Optional[str],
|
|
138
138
|
reported_at: Union[str, datetime, None],
|
|
139
139
|
) -> dict[str, Any]:
|
|
140
140
|
"""The body of one report. A ``datetime`` is sent as an ISO string; pass a
|
|
141
141
|
timezone-aware one, since the server reads a naive timestamp as its own."""
|
|
142
142
|
body: dict[str, Any] = {"outcome": outcome}
|
|
143
|
-
if estado is not None:
|
|
144
|
-
body["estado"] = estado
|
|
145
143
|
if reason is not None:
|
|
146
144
|
body["reason"] = reason
|
|
145
|
+
if context is not None:
|
|
146
|
+
body["context"] = context
|
|
147
147
|
if reported_at is not None:
|
|
148
148
|
body["reported_at"] = (
|
|
149
149
|
reported_at.isoformat() if isinstance(reported_at, datetime) else reported_at
|
|
@@ -159,8 +159,8 @@ def _feedback_chunks(reports: Sequence[FeedbackReport]) -> Iterator[list[dict[st
|
|
|
159
159
|
"solve_id": report["solve_id"],
|
|
160
160
|
**_feedback_fields(
|
|
161
161
|
report["outcome"],
|
|
162
|
-
report.get("estado"),
|
|
163
162
|
report.get("reason"),
|
|
163
|
+
report.get("context"),
|
|
164
164
|
report.get("reported_at"),
|
|
165
165
|
),
|
|
166
166
|
}
|
|
@@ -511,8 +511,8 @@ class Feedbacks:
|
|
|
511
511
|
solve_id: str,
|
|
512
512
|
*,
|
|
513
513
|
outcome: FeedbackOutcome,
|
|
514
|
-
estado: Optional[bool] = None,
|
|
515
514
|
reason: Optional[str] = None,
|
|
515
|
+
context: Optional[str] = None,
|
|
516
516
|
reported_at: Union[str, datetime, None] = None,
|
|
517
517
|
) -> Feedback:
|
|
518
518
|
"""Report one solve's downstream outcome and return what was recorded.
|
|
@@ -527,7 +527,7 @@ class Feedbacks:
|
|
|
527
527
|
payload = self._client._request(
|
|
528
528
|
"POST",
|
|
529
529
|
f"/v1/solves/{solve_id}/feedback",
|
|
530
|
-
json=_feedback_fields(outcome,
|
|
530
|
+
json=_feedback_fields(outcome, reason, context, reported_at),
|
|
531
531
|
)
|
|
532
532
|
return Feedback._from_dict(payload)
|
|
533
533
|
|
|
@@ -1012,8 +1012,8 @@ class AsyncFeedbacks:
|
|
|
1012
1012
|
solve_id: str,
|
|
1013
1013
|
*,
|
|
1014
1014
|
outcome: FeedbackOutcome,
|
|
1015
|
-
estado: Optional[bool] = None,
|
|
1016
1015
|
reason: Optional[str] = None,
|
|
1016
|
+
context: Optional[str] = None,
|
|
1017
1017
|
reported_at: Union[str, datetime, None] = None,
|
|
1018
1018
|
) -> Feedback:
|
|
1019
1019
|
"""Report one solve's downstream outcome and return what was recorded.
|
|
@@ -1026,7 +1026,7 @@ class AsyncFeedbacks:
|
|
|
1026
1026
|
payload = await self._client._request(
|
|
1027
1027
|
"POST",
|
|
1028
1028
|
f"/v1/solves/{solve_id}/feedback",
|
|
1029
|
-
json=_feedback_fields(outcome,
|
|
1029
|
+
json=_feedback_fields(outcome, reason, context, reported_at),
|
|
1030
1030
|
)
|
|
1031
1031
|
return Feedback._from_dict(payload)
|
|
1032
1032
|
|
|
@@ -135,15 +135,18 @@ class FeedbackReport(_FeedbackReportRequired, total=False):
|
|
|
135
135
|
"""One verdict to report, as passed to ``client.feedback.report_many``.
|
|
136
136
|
|
|
137
137
|
``solve_id`` is the id from ``solves.create`` / ``solve()``, and must be
|
|
138
|
-
your own solve that produced a token. ``
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
138
|
+
your own solve that produced a token. ``reason`` is the code or message your
|
|
139
|
+
target gave you when it refused the token (e.g. ``"invalid-response"``),
|
|
140
|
+
truncated to 512 chars server-side. ``context`` is optional free text with no
|
|
141
|
+
schema — anything about the attempt you think would help us diagnose it,
|
|
142
|
+
truncated to 2000 chars. Neither is parsed; they are what a human reads when
|
|
143
|
+
you raise a ticket, and they carry the half of a rejection NoneCap cannot
|
|
144
|
+
observe on its own. ``reported_at`` is advisory only — the server stamps its
|
|
145
|
+
own timestamps.
|
|
143
146
|
"""
|
|
144
147
|
|
|
145
|
-
estado: Optional[bool]
|
|
146
148
|
reason: Optional[str]
|
|
149
|
+
context: Optional[str]
|
|
147
150
|
reported_at: Union[str, datetime, None]
|
|
148
151
|
|
|
149
152
|
|
|
@@ -154,8 +157,8 @@ class Feedback:
|
|
|
154
157
|
object: str
|
|
155
158
|
solve_id: str
|
|
156
159
|
outcome: FeedbackOutcome
|
|
157
|
-
estado: Optional[bool]
|
|
158
160
|
reason: Optional[str]
|
|
161
|
+
context: Optional[str]
|
|
159
162
|
reported_at: Optional[str]
|
|
160
163
|
report_count: int
|
|
161
164
|
"""How many times this solve's verdict has been written. 1 on first report."""
|
|
@@ -168,8 +171,8 @@ class Feedback:
|
|
|
168
171
|
object=data.get("object", "feedback"),
|
|
169
172
|
solve_id=data["solve_id"],
|
|
170
173
|
outcome=data["outcome"],
|
|
171
|
-
estado=data.get("estado"),
|
|
172
174
|
reason=data.get("reason"),
|
|
175
|
+
context=data.get("context"),
|
|
173
176
|
reported_at=data.get("reported_at"),
|
|
174
177
|
report_count=int(data.get("report_count", 1)),
|
|
175
178
|
created_at=data.get("created_at", ""),
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.4.0"
|
|
@@ -33,8 +33,8 @@ def feedback_payload(**overrides: Any) -> dict[str, Any]:
|
|
|
33
33
|
"object": "feedback",
|
|
34
34
|
"solve_id": "solve_1",
|
|
35
35
|
"outcome": "accepted",
|
|
36
|
-
"estado": None,
|
|
37
36
|
"reason": None,
|
|
37
|
+
"context": None,
|
|
38
38
|
"reported_at": None,
|
|
39
39
|
"report_count": 1,
|
|
40
40
|
"created_at": "2026-08-01T00:00:00Z",
|
|
@@ -67,32 +67,33 @@ def batch_payload(request: httpx.Request) -> httpx.Response:
|
|
|
67
67
|
|
|
68
68
|
class TestReport:
|
|
69
69
|
def test_posts_to_the_solves_feedback_path(self) -> None:
|
|
70
|
-
script = Script((200, feedback_payload(outcome="rejected",
|
|
70
|
+
script = Script((200, feedback_payload(outcome="rejected", context="3rd retry")))
|
|
71
71
|
nc = client_for(script)
|
|
72
72
|
result = nc.feedback.report(
|
|
73
|
-
"solve_1", outcome="rejected",
|
|
73
|
+
"solve_1", outcome="rejected", reason="invalid-response", context="3rd retry"
|
|
74
74
|
)
|
|
75
75
|
|
|
76
76
|
assert script.requests[0].method == "POST"
|
|
77
77
|
assert script.requests[0].url.path == "/v1/solves/solve_1/feedback"
|
|
78
78
|
assert script.body_of(0) == {
|
|
79
79
|
"outcome": "rejected",
|
|
80
|
-
"
|
|
81
|
-
"
|
|
80
|
+
"reason": "invalid-response",
|
|
81
|
+
"context": "3rd retry",
|
|
82
82
|
}
|
|
83
83
|
assert result.outcome == "rejected"
|
|
84
|
-
assert result.
|
|
84
|
+
assert result.context == "3rd retry"
|
|
85
85
|
|
|
86
86
|
def test_omits_fields_the_caller_left_unset(self) -> None:
|
|
87
87
|
script = Script((200, feedback_payload()))
|
|
88
88
|
client_for(script).feedback.report("solve_1", outcome="accepted")
|
|
89
89
|
assert script.body_of(0) == {"outcome": "accepted"}
|
|
90
90
|
|
|
91
|
-
def
|
|
92
|
-
# `if
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
91
|
+
def test_sends_an_empty_context(self) -> None:
|
|
92
|
+
# `if context:` would drop an empty string, which is a deliberate "I have
|
|
93
|
+
# nothing to add" rather than an omission. Only None means unset.
|
|
94
|
+
script = Script((200, feedback_payload(context="")))
|
|
95
|
+
client_for(script).feedback.report("solve_1", outcome="accepted", context="")
|
|
96
|
+
assert script.body_of(0) == {"outcome": "accepted", "context": ""}
|
|
96
97
|
|
|
97
98
|
def test_serializes_a_datetime_reported_at(self) -> None:
|
|
98
99
|
script = Script((200, feedback_payload()))
|
|
@@ -123,8 +124,8 @@ class TestReportMany:
|
|
|
123
124
|
script = Script(batch_payload)
|
|
124
125
|
batch = client_for(script).feedback.report_many(
|
|
125
126
|
[
|
|
126
|
-
{"solve_id": "solve_1", "outcome": "accepted"
|
|
127
|
-
{"solve_id": "solve_2", "outcome": "rejected", "
|
|
127
|
+
{"solve_id": "solve_1", "outcome": "accepted"},
|
|
128
|
+
{"solve_id": "solve_2", "outcome": "rejected", "context": "3rd retry"},
|
|
128
129
|
]
|
|
129
130
|
)
|
|
130
131
|
|
|
@@ -132,8 +133,8 @@ class TestReportMany:
|
|
|
132
133
|
assert script.requests[0].url.path == "/v1/feedback"
|
|
133
134
|
assert script.body_of(0) == {
|
|
134
135
|
"feedback": [
|
|
135
|
-
{"solve_id": "solve_1", "outcome": "accepted"
|
|
136
|
-
{"solve_id": "solve_2", "outcome": "rejected", "
|
|
136
|
+
{"solve_id": "solve_1", "outcome": "accepted"},
|
|
137
|
+
{"solve_id": "solve_2", "outcome": "rejected", "context": "3rd retry"},
|
|
137
138
|
]
|
|
138
139
|
}
|
|
139
140
|
assert batch.recorded == 2
|
|
@@ -56,10 +56,10 @@ async def positive_cases_async() -> None:
|
|
|
56
56
|
|
|
57
57
|
def feedback_cases() -> None:
|
|
58
58
|
_f: Feedback = nc.feedback.report("solve_1", outcome="accepted")
|
|
59
|
-
nc.feedback.report("solve_1", outcome="rejected",
|
|
59
|
+
nc.feedback.report("solve_1", outcome="rejected", reason="why", context="ctx")
|
|
60
60
|
reports: list[FeedbackReport] = [
|
|
61
61
|
{"solve_id": "solve_1", "outcome": "accepted"},
|
|
62
|
-
{"solve_id": "solve_2", "outcome": "rejected", "
|
|
62
|
+
{"solve_id": "solve_2", "outcome": "rejected", "reason": "why", "context": "ctx"},
|
|
63
63
|
]
|
|
64
64
|
_b: FeedbackBatch = nc.feedback.report_many(reports)
|
|
65
65
|
# An unknown outcome must not type-check.
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.3.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
|