Skip to content

Commit afe2fea

Browse files
feat: Polish and publish the Config Registry API
Stainless-Generated-From: c13de5a1ff00481bd0125cfd74527f4bae1a301f
1 parent 37d5e9c commit afe2fea

8 files changed

Lines changed: 191 additions & 20 deletions

File tree

.stats.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
configured_endpoints: 163
1+
configured_endpoints: 164

api.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ Methods:
109109

110110
- <code title="get /config-registry/analyses/{id}">client.config_registry.analyses.<a href="./src/kernel/resources/config_registry/analyses.py">retrieve</a>(id) -> <a href="./src/kernel/types/config_registry_response.py">ConfigRegistryResponse</a></code>
111111
- <code title="get /config-registry/analyses">client.config_registry.analyses.<a href="./src/kernel/resources/config_registry/analyses.py">list</a>(\*\*<a href="src/kernel/types/config_registry/analysis_list_params.py">params</a>) -> <a href="./src/kernel/types/analysis_summary.py">SyncOffsetPagination[AnalysisSummary]</a></code>
112+
- <code title="post /config-registry/analyses/{id}/cancel">client.config_registry.analyses.<a href="./src/kernel/resources/config_registry/analyses.py">cancel</a>(id) -> <a href="./src/kernel/types/config_registry_response.py">ConfigRegistryResponse</a></code>
112113

113114
# Browsers
114115

src/kernel/resources/config_registry/analyses.py

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,43 @@ def list(
126126
model=AnalysisSummary,
127127
)
128128

129+
def cancel(
130+
self,
131+
id: str,
132+
*,
133+
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
134+
# The extra values given here take precedence over values defined on the client or passed to this method.
135+
extra_headers: Headers | None = None,
136+
extra_query: Query | None = None,
137+
extra_body: Body | None = None,
138+
timeout: float | httpx.Timeout | None | NotGiven = not_given,
139+
) -> ConfigRegistryResponse:
140+
"""Requests cancellation of a running project-scoped analysis.
141+
142+
Cancellation is
143+
asynchronous; poll the analysis until its status becomes canceled. Repeating the
144+
request after the analysis reaches a terminal state returns the existing
145+
outcome.
146+
147+
Args:
148+
extra_headers: Send extra headers
149+
150+
extra_query: Add additional query parameters to the request
151+
152+
extra_body: Add additional JSON properties to the request
153+
154+
timeout: Override the client-level default timeout for this request, in seconds
155+
"""
156+
if not id:
157+
raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
158+
return self._post(
159+
path_template("/config-registry/analyses/{id}/cancel", id=id),
160+
options=make_request_options(
161+
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
162+
),
163+
cast_to=ConfigRegistryResponse,
164+
)
165+
129166

130167
class AsyncAnalysesResource(AsyncAPIResource):
131168
"""Resolve browser and proxy recommendations for bot-protected sites."""
@@ -230,6 +267,43 @@ def list(
230267
model=AnalysisSummary,
231268
)
232269

270+
async def cancel(
271+
self,
272+
id: str,
273+
*,
274+
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
275+
# The extra values given here take precedence over values defined on the client or passed to this method.
276+
extra_headers: Headers | None = None,
277+
extra_query: Query | None = None,
278+
extra_body: Body | None = None,
279+
timeout: float | httpx.Timeout | None | NotGiven = not_given,
280+
) -> ConfigRegistryResponse:
281+
"""Requests cancellation of a running project-scoped analysis.
282+
283+
Cancellation is
284+
asynchronous; poll the analysis until its status becomes canceled. Repeating the
285+
request after the analysis reaches a terminal state returns the existing
286+
outcome.
287+
288+
Args:
289+
extra_headers: Send extra headers
290+
291+
extra_query: Add additional query parameters to the request
292+
293+
extra_body: Add additional JSON properties to the request
294+
295+
timeout: Override the client-level default timeout for this request, in seconds
296+
"""
297+
if not id:
298+
raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
299+
return await self._post(
300+
path_template("/config-registry/analyses/{id}/cancel", id=id),
301+
options=make_request_options(
302+
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
303+
),
304+
cast_to=ConfigRegistryResponse,
305+
)
306+
233307

234308
class AnalysesResourceWithRawResponse:
235309
def __init__(self, analyses: AnalysesResource) -> None:
@@ -241,6 +315,9 @@ def __init__(self, analyses: AnalysesResource) -> None:
241315
self.list = to_raw_response_wrapper(
242316
analyses.list,
243317
)
318+
self.cancel = to_raw_response_wrapper(
319+
analyses.cancel,
320+
)
244321

245322

246323
class AsyncAnalysesResourceWithRawResponse:
@@ -253,6 +330,9 @@ def __init__(self, analyses: AsyncAnalysesResource) -> None:
253330
self.list = async_to_raw_response_wrapper(
254331
analyses.list,
255332
)
333+
self.cancel = async_to_raw_response_wrapper(
334+
analyses.cancel,
335+
)
256336

257337

258338
class AnalysesResourceWithStreamingResponse:
@@ -265,6 +345,9 @@ def __init__(self, analyses: AnalysesResource) -> None:
265345
self.list = to_streamed_response_wrapper(
266346
analyses.list,
267347
)
348+
self.cancel = to_streamed_response_wrapper(
349+
analyses.cancel,
350+
)
268351

269352

270353
class AsyncAnalysesResourceWithStreamingResponse:
@@ -277,3 +360,6 @@ def __init__(self, analyses: AsyncAnalysesResource) -> None:
277360
self.list = async_to_streamed_response_wrapper(
278361
analyses.list,
279362
)
363+
self.cancel = async_to_streamed_response_wrapper(
364+
analyses.cancel,
365+
)

src/kernel/resources/config_registry/config_registry.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -190,8 +190,9 @@ def resolve(
190190
any non-HTTPS destination as off-site and will not drive an http one. Kernel
191191
uses it to drive the browser further into the site, where it can observe
192192
protections that only appear once a session interacts. When this target already
193-
has a verified configuration, the run confirms that one instead of re-deriving
194-
the whole matrix, so supplying an intent narrows what can be recommended.
193+
has a recommended configuration, the run confirms that one instead of
194+
re-deriving the whole matrix, so supplying an intent narrows what can be
195+
recommended.
195196
196197
extra_headers: Send extra headers
197198
@@ -374,8 +375,9 @@ async def resolve(
374375
any non-HTTPS destination as off-site and will not drive an http one. Kernel
375376
uses it to drive the browser further into the site, where it can observe
376377
protections that only appear once a session interacts. When this target already
377-
has a verified configuration, the run confirms that one instead of re-deriving
378-
the whole matrix, so supplying an intent narrows what can be recommended.
378+
has a recommended configuration, the run confirms that one instead of
379+
re-deriving the whole matrix, so supplying an intent narrows what can be
380+
recommended.
379381
380382
extra_headers: Send extra headers
381383

src/kernel/types/config_registry_resolve_params.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ class ConfigRegistryResolveParams(TypedDict, total=False):
2727
any non-HTTPS destination as off-site and will not drive an http one. Kernel
2828
uses it to drive the browser further into the site, where it can observe
2929
protections that only appear once a session interacts. When this target already
30-
has a verified configuration, the run confirms that one instead of re-deriving
31-
the whole matrix, so supplying an intent narrows what can be recommended.
30+
has a recommended configuration, the run confirms that one instead of
31+
re-deriving the whole matrix, so supplying an intent narrows what can be
32+
recommended.
3233
"""

src/kernel/types/evidence.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@ class Evidence(BaseModel):
3030
success_rate: float
3131
"""Accessed trials divided by judged trials. Inconclusive trials are excluded."""
3232

33-
last_verified_at: Optional[datetime] = None
34-
"""Most recent contributing run where this config met the success threshold.
35-
36-
Omitted for knowledge assembled from runs that did not independently meet the
37-
threshold.
33+
last_supported_at: Optional[datetime] = None
34+
"""
35+
Most recent contributing run whose evidence supported recommending this
36+
configuration. Omitted when no individual run independently met the
37+
recommendation threshold.
3838
"""

src/kernel/types/recommendation.py

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,11 @@ class Recommendation(BaseModel):
1717
evidence: Evidence
1818

1919
match_scope: Literal["exact", "host", "domain"]
20-
"""Specificity of knowledge matched for this recommendation."""
20+
"""Specificity of knowledge matched for this recommendation.
21+
22+
Exact matches use knowledge for the requested target; host and domain matches
23+
use broader fallback knowledge.
24+
"""
2125

2226
matched_target: str
2327
"""Target value that supplied the recommendation."""
@@ -26,10 +30,3 @@ class Recommendation(BaseModel):
2630
"""Proxy recipe for the recommended browser."""
2731

2832
type: Literal["recommendation"]
29-
30-
verification: Literal["verified", "inferred"]
31-
"""
32-
Exact matches meet the evidence threshold; host and domain fallbacks are
33-
inferred. Check evidence.last_verified_at for successful verification age and
34-
last_observed_at for the latest evidence.
35-
"""

tests/api_resources/config_registry/test_analyses.py

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,48 @@ def test_streaming_response_list(self, client: Kernel) -> None:
9898

9999
assert cast(Any, response.is_closed) is True
100100

101+
@pytest.mark.skip(reason="Mock server tests are disabled")
102+
@parametrize
103+
def test_method_cancel(self, client: Kernel) -> None:
104+
analysis = client.config_registry.analyses.cancel(
105+
"id",
106+
)
107+
assert_matches_type(ConfigRegistryResponse, analysis, path=["response"])
108+
109+
@pytest.mark.skip(reason="Mock server tests are disabled")
110+
@parametrize
111+
def test_raw_response_cancel(self, client: Kernel) -> None:
112+
response = client.config_registry.analyses.with_raw_response.cancel(
113+
"id",
114+
)
115+
116+
assert response.is_closed is True
117+
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
118+
analysis = response.parse()
119+
assert_matches_type(ConfigRegistryResponse, analysis, path=["response"])
120+
121+
@pytest.mark.skip(reason="Mock server tests are disabled")
122+
@parametrize
123+
def test_streaming_response_cancel(self, client: Kernel) -> None:
124+
with client.config_registry.analyses.with_streaming_response.cancel(
125+
"id",
126+
) as response:
127+
assert not response.is_closed
128+
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
129+
130+
analysis = response.parse()
131+
assert_matches_type(ConfigRegistryResponse, analysis, path=["response"])
132+
133+
assert cast(Any, response.is_closed) is True
134+
135+
@pytest.mark.skip(reason="Mock server tests are disabled")
136+
@parametrize
137+
def test_path_params_cancel(self, client: Kernel) -> None:
138+
with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
139+
client.config_registry.analyses.with_raw_response.cancel(
140+
"",
141+
)
142+
101143

102144
class TestAsyncAnalyses:
103145
parametrize = pytest.mark.parametrize(
@@ -183,3 +225,45 @@ async def test_streaming_response_list(self, async_client: AsyncKernel) -> None:
183225
assert_matches_type(AsyncOffsetPagination[AnalysisSummary], analysis, path=["response"])
184226

185227
assert cast(Any, response.is_closed) is True
228+
229+
@pytest.mark.skip(reason="Mock server tests are disabled")
230+
@parametrize
231+
async def test_method_cancel(self, async_client: AsyncKernel) -> None:
232+
analysis = await async_client.config_registry.analyses.cancel(
233+
"id",
234+
)
235+
assert_matches_type(ConfigRegistryResponse, analysis, path=["response"])
236+
237+
@pytest.mark.skip(reason="Mock server tests are disabled")
238+
@parametrize
239+
async def test_raw_response_cancel(self, async_client: AsyncKernel) -> None:
240+
response = await async_client.config_registry.analyses.with_raw_response.cancel(
241+
"id",
242+
)
243+
244+
assert response.is_closed is True
245+
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
246+
analysis = await response.parse()
247+
assert_matches_type(ConfigRegistryResponse, analysis, path=["response"])
248+
249+
@pytest.mark.skip(reason="Mock server tests are disabled")
250+
@parametrize
251+
async def test_streaming_response_cancel(self, async_client: AsyncKernel) -> None:
252+
async with async_client.config_registry.analyses.with_streaming_response.cancel(
253+
"id",
254+
) as response:
255+
assert not response.is_closed
256+
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
257+
258+
analysis = await response.parse()
259+
assert_matches_type(ConfigRegistryResponse, analysis, path=["response"])
260+
261+
assert cast(Any, response.is_closed) is True
262+
263+
@pytest.mark.skip(reason="Mock server tests are disabled")
264+
@parametrize
265+
async def test_path_params_cancel(self, async_client: AsyncKernel) -> None:
266+
with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
267+
await async_client.config_registry.analyses.with_raw_response.cancel(
268+
"",
269+
)

0 commit comments

Comments
 (0)