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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nonecap
3
- Version: 0.3.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, freeform
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, freeform
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, estado, reason, reported_at),
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, estado, reason, reported_at),
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. ``estado`` is the raw downstream
139
- boolean when your target has one (OSIPTEL's ``estado``: ``False`` =
140
- accepted); the API rejects an item whose ``estado`` disagrees with its
141
- ``outcome``. ``reason`` is freeform and truncated to 512 chars server-side.
142
- ``reported_at`` is advisory only — the server stamps its own timestamps.
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", estado=True)))
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", estado=True, reason="documento no vigente"
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
- "estado": True,
81
- "reason": "documento no vigente",
80
+ "reason": "invalid-response",
81
+ "context": "3rd retry",
82
82
  }
83
83
  assert result.outcome == "rejected"
84
- assert result.estado is True
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 test_sends_estado_false(self) -> None:
92
- # `if estado:` would drop the accepted signal — the one that matters.
93
- script = Script((200, feedback_payload(estado=False)))
94
- client_for(script).feedback.report("solve_1", outcome="accepted", estado=False)
95
- assert script.body_of(0) == {"outcome": "accepted", "estado": False}
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", "estado": False},
127
- {"solve_id": "solve_2", "outcome": "rejected", "estado": True},
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", "estado": False},
136
- {"solve_id": "solve_2", "outcome": "rejected", "estado": True},
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", estado=True, reason="why")
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", "estado": True, "reason": "why"},
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