Skip to content

Commit b2cad78

Browse files
authored
fix(api): Update List Seer AI Models to reflect that it is regional (#104530)
<!-- Describe your PR here. --> The "List Seer AI Models" api reference did not specify a regional url. Historically, these were added in manually, but we have since switched to spectacular to create our json. I have added a servers attribute to the base api and a catch that will add the attribute to the method info if servers are specified. The watch command to test also had a legacy error that I fixed to properly see the changes. (This was moved to a separate PR as a frontend change) To test follow these instructions: https://develop.sentry.dev/backend/api/public/#building-and-testing-locally <!-- Sentry employees and contractors can delete or ignore the following. --> ### Legal Boilerplate Look, I get it. The entity doing business as "Sentry" was incorporated in the State of Delaware in 2015 as Functional Software, Inc. and is gonna need some rights from me in order to utilize my contributions in this here PR. So here's the deal: I retain all rights, title and interest in and to my contributions, and by keeping this boilerplate intact I confirm that Sentry can use, modify, copy, and redistribute my contributions, under Sentry's choice of terms.
1 parent 63d5897 commit b2cad78

File tree

4 files changed

+66
-1
lines changed

4 files changed

+66
-1
lines changed

src/sentry/api/base.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,7 @@ class Endpoint(APIView):
227227
publish_status: dict[HTTP_METHOD_NAME, ApiPublishStatus] = {}
228228
rate_limits: RateLimitConfig = DEFAULT_RATE_LIMIT_CONFIG
229229
enforce_rate_limit: bool = settings.SENTRY_RATELIMITER_ENABLED
230+
servers: list[dict[str, Any]] | None = None
230231

231232
def build_cursor_link(self, request: HttpRequest, name: str, cursor: Cursor) -> str:
232233
if request.GET.get("cursor") is None:

src/sentry/api/endpoints/seer_models.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ class SeerModelsEndpoint(Endpoint):
5050
}
5151
owner = ApiOwner.ML_AI
5252
permission_classes = ()
53+
servers = [{"url": "https://{region}.sentry.io"}]
5354

5455
enforce_rate_limit = True
5556
rate_limits = RateLimitConfig(
@@ -72,6 +73,9 @@ def get(self, request: Request) -> Response:
7273
7374
Returns the list of AI models that are currently used in production in Seer.
7475
This endpoint does not require authentication and can be used to discover which models Seer uses.
76+
77+
Requests to this endpoint should use the region-specific domain
78+
eg. `us.sentry.io` or `de.sentry.io`
7579
"""
7680
cached_data = cache.get(SEER_MODELS_CACHE_KEY)
7781
if cached_data is not None:

src/sentry/apidocs/hooks.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,10 +103,21 @@ class CustomGenerator(SchemaGenerator):
103103
endpoint_inspector_cls = CustomEndpointEnumerator
104104

105105

106+
# Collected during preprocessing, used in postprocessing
107+
_ENDPOINT_SERVERS: dict[str, list[dict[str, Any]]] = {}
108+
109+
106110
def custom_preprocessing_hook(endpoints: Any) -> Any: # TODO: organize method, rename
111+
_ENDPOINT_SERVERS.clear()
112+
107113
filtered = []
108114
ownership_data: dict[ApiOwner, dict] = {}
109115
for path, path_regex, method, callback in endpoints:
116+
# Collect servers from endpoint class for postprocessing
117+
endpoint_servers = getattr(callback.view_class, "servers", None)
118+
if endpoint_servers is not None:
119+
_ENDPOINT_SERVERS[path] = endpoint_servers
120+
110121
owner_team = callback.view_class.owner
111122
if owner_team not in ownership_data:
112123
ownership_data[owner_team] = {
@@ -222,6 +233,12 @@ def _validate_request_body(
222233

223234

224235
def custom_postprocessing_hook(result: Any, generator: Any, **kwargs: Any) -> Any:
236+
# Add servers override from endpoint class definitions
237+
for path, servers in _ENDPOINT_SERVERS.items():
238+
if path in result["paths"]:
239+
for method_info in result["paths"][path].values():
240+
method_info["servers"] = servers
241+
225242
_fix_issue_paths(result)
226243

227244
# Fetch schema component references

tests/apidocs/test_hooks.py

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,49 @@
11
from unittest import TestCase
22

3-
from sentry.apidocs.hooks import custom_postprocessing_hook
3+
from sentry.apidocs.hooks import _ENDPOINT_SERVERS, custom_postprocessing_hook
4+
5+
6+
class EndpointServersTest(TestCase):
7+
def setUp(self) -> None:
8+
_ENDPOINT_SERVERS.clear()
9+
10+
def tearDown(self) -> None:
11+
_ENDPOINT_SERVERS.clear()
12+
13+
def test_servers_applied_to_endpoint(self) -> None:
14+
"""Test that servers from _ENDPOINT_SERVERS are applied to matching paths."""
15+
_ENDPOINT_SERVERS["/api/0/seer/models/"] = [{"url": "https://{region}.sentry.io"}]
16+
17+
result = {
18+
"components": {"schemas": {}},
19+
"paths": {
20+
"/api/0/seer/models/": {
21+
"get": {
22+
"tags": ["Seer"],
23+
"description": "Get models",
24+
"operationId": "get models",
25+
"parameters": [],
26+
}
27+
},
28+
"/api/0/other/endpoint/": {
29+
"get": {
30+
"tags": ["Events"],
31+
"description": "Other endpoint",
32+
"operationId": "get other",
33+
"parameters": [],
34+
}
35+
},
36+
},
37+
}
38+
39+
processed = custom_postprocessing_hook(result, None)
40+
41+
# Servers should be applied to the matching endpoint
42+
assert processed["paths"]["/api/0/seer/models/"]["get"]["servers"] == [
43+
{"url": "https://{region}.sentry.io"}
44+
]
45+
# Servers should NOT be applied to non-matching endpoint
46+
assert "servers" not in processed["paths"]["/api/0/other/endpoint/"]["get"]
447

548

649
class FixIssueRoutesTest(TestCase):

0 commit comments

Comments
 (0)