Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 14 additions & 13 deletions src/sentry/api/endpoints/seer_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
import logging
from typing import TypedDict

import requests
from django.conf import settings
from drf_spectacular.utils import extend_schema
from rest_framework.exceptions import APIException
from rest_framework.request import Request
Expand All @@ -15,9 +13,12 @@
from sentry.api.base import Endpoint, region_silo_endpoint
from sentry.apidocs.utils import inline_sentry_response_serializer
from sentry.ratelimits.config import RateLimitConfig
from sentry.seer.signed_seer_api import sign_with_seer_secret
from sentry.seer.models import SeerApiError
from sentry.seer.signed_seer_api import (
make_signed_seer_api_request,
seer_autofix_default_connection_pool,
)
from sentry.types.ratelimit import RateLimit, RateLimitCategory
from sentry.utils import json
from sentry.utils.cache import cache

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -84,23 +85,23 @@ def get(self, request: Request) -> Response:
path = "/v1/models"

try:
response = requests.get(
f"{settings.SEER_AUTOFIX_URL}{path}",
headers={
"content-type": "application/json;charset=utf-8",
**sign_with_seer_secret(b""),
},
response = make_signed_seer_api_request(
seer_autofix_default_connection_pool,
path,
b"",
timeout=5,
method="GET",
)
response.raise_for_status()
if response.status >= 400:
raise SeerApiError("Seer request failed", response.status)

data = response.json()
cache.set(SEER_MODELS_CACHE_KEY, data, SEER_MODELS_CACHE_TIMEOUT)
return Response(data, status=200)

except requests.exceptions.Timeout:
except TimeoutError:
logger.warning("Timeout when fetching models from Seer")
raise SeerTimeoutError()
except (requests.exceptions.RequestException, json.JSONDecodeError):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Catching wrong TimeoutError type; urllib3 timeouts uncaught

High Severity

The except TimeoutError clause catches Python's built-in TimeoutError, but make_signed_seer_api_request uses urllib3 which raises urllib3.exceptions.TimeoutError — a completely separate class that does not inherit from the built-in TimeoutError. As a result, urllib3 timeout errors will never be caught here and will instead fall through to except Exception, returning a 502 SeerConnectionError instead of the intended 504 SeerTimeoutError. Notably, uptime/seer_assertions.py in this same PR correctly imports and catches urllib3.exceptions.TimeoutError as Urllib3TimeoutError.

Fix in Cursor Fix in Web

except Exception:
logger.exception("Error fetching models from Seer")
raise SeerConnectionError()
27 changes: 16 additions & 11 deletions src/sentry/seer/services/test_generation/impl.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
import orjson
import requests
from django.conf import settings

from sentry.integrations.types import IntegrationProviderSlug
from sentry.seer.services.test_generation.model import CreateUnitTestResponse
from sentry.seer.services.test_generation.service import TestGenerationService
from sentry.seer.signed_seer_api import (
make_signed_seer_api_request,
seer_autofix_default_connection_pool,
)


class RegionBackedTestGenerationService(TestGenerationService):
def start_unit_test_generation(
self, *, region_name: str, github_org: str, repo: str, pr_id: int, external_id: str
) -> CreateUnitTestResponse:
url = f"{settings.SEER_AUTOFIX_URL}/v1/automation/codegen/unit-tests"
body = orjson.dumps(
{
"repo": {
Expand All @@ -25,15 +26,19 @@ def start_unit_test_generation(
option=orjson.OPT_NON_STR_KEYS,
)

response = requests.post(
url,
data=body,
headers={
"content-type": "application/json;charset=utf-8",
},
response = make_signed_seer_api_request(
seer_autofix_default_connection_pool,
"/v1/automation/codegen/unit-tests",
body,
)

if response.status_code == 200:
if response.status == 200:
return CreateUnitTestResponse()
else:
return CreateUnitTestResponse(error_detail=response.text)
try:
error_detail = response.data.decode("utf-8") if response.data else None
except (AttributeError, UnicodeDecodeError):
error_detail = None
return CreateUnitTestResponse(
error_detail=error_detail or f"Request failed with status {response.status}"
)
22 changes: 11 additions & 11 deletions src/sentry/seer/supergroups.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
from __future__ import annotations

import orjson
import requests
from django.conf import settings

from sentry.seer.signed_seer_api import sign_with_seer_secret
from sentry.seer.models import SeerApiError
from sentry.seer.signed_seer_api import (
make_signed_seer_api_request,
seer_autofix_default_connection_pool,
)


def trigger_supergroups_embedding(
Expand All @@ -21,13 +23,11 @@ def trigger_supergroups_embedding(
}
)

response = requests.post(
f"{settings.SEER_AUTOFIX_URL}{path}",
data=body,
headers={
"content-type": "application/json;charset=utf-8",
**sign_with_seer_secret(body),
},
response = make_signed_seer_api_request(
seer_autofix_default_connection_pool,
path,
body,
timeout=5,
)
response.raise_for_status()
if response.status >= 400:
raise SeerApiError("Seer request failed", response.status)
23 changes: 11 additions & 12 deletions src/sentry/seer/trace_summary.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,16 @@
from typing import Any

import orjson
import requests
from django.conf import settings
from django.contrib.auth.models import AnonymousUser

from sentry import features
from sentry.api.serializers.rest_framework.base import convert_dict_key_case, snake_to_camel_case
from sentry.models.organization import Organization
from sentry.seer.models import SummarizeTraceResponse
from sentry.seer.signed_seer_api import sign_with_seer_secret
from sentry.seer.models import SeerApiError, SummarizeTraceResponse
from sentry.seer.signed_seer_api import (
make_signed_seer_api_request,
seer_summarization_default_connection_pool,
)
from sentry.users.models.user import User
from sentry.users.services.user.model import RpcUser
from sentry.utils.cache import cache
Expand Down Expand Up @@ -79,14 +80,12 @@ def _call_seer(
option=orjson.OPT_NON_STR_KEYS,
)

response = requests.post(
f"{settings.SEER_SUMMARIZATION_URL}{path}",
data=body,
headers={
"content-type": "application/json;charset=utf-8",
**sign_with_seer_secret(body),
},
response = make_signed_seer_api_request(
seer_summarization_default_connection_pool,
path,
body,
)
response.raise_for_status()
if response.status >= 400:
raise SeerApiError("Seer request failed", response.status)

return SummarizeTraceResponse.validate(response.json())
24 changes: 12 additions & 12 deletions src/sentry/tasks/explorer_context_engine_tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@
from datetime import UTC, datetime, timedelta, timezone

import orjson
import requests
import sentry_sdk
from django.conf import settings

from sentry import options
from sentry.constants import ObjectStatus
Expand All @@ -27,7 +25,11 @@
_query_service_dependencies,
_send_to_seer,
)
from sentry.seer.signed_seer_api import sign_with_seer_secret
from sentry.seer.models import SeerApiError
from sentry.seer.signed_seer_api import (
make_signed_seer_api_request,
seer_autofix_default_connection_pool,
)
from sentry.tasks.base import instrumented_task
from sentry.taskworker.namespaces import seer_tasks

Expand Down Expand Up @@ -103,17 +105,15 @@ def index_org_project_knowledge(org_id: int) -> None:
path = "/v1/automation/explorer/index/org-project-knowledge"

try:
response = requests.post(
f"{settings.SEER_AUTOFIX_URL}{path}",
data=body,
headers={
"content-type": "application/json;charset=utf-8",
**sign_with_seer_secret(body),
},
response = make_signed_seer_api_request(
seer_autofix_default_connection_pool,
path,
body,
timeout=30,
)
response.raise_for_status()
except requests.RequestException:
if response.status >= 400:
raise SeerApiError("Seer request failed", response.status)
except Exception:
logger.exception(
"Failed to call Seer org-project-knowledge endpoint",
extra={"org_id": org_id, "num_projects": len(project_data)},
Expand Down
22 changes: 11 additions & 11 deletions src/sentry/tasks/seer.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
import logging

import orjson
import requests
from django.conf import settings

from sentry.seer.signed_seer_api import sign_with_seer_secret
from sentry.seer.models import SeerApiError
from sentry.seer.signed_seer_api import (
make_signed_seer_api_request,
seer_autofix_default_connection_pool,
)
from sentry.silo.base import SiloMode
from sentry.tasks.base import instrumented_task
from sentry.taskworker.namespaces import seer_tasks
Expand Down Expand Up @@ -40,15 +42,13 @@ def cleanup_seer_repository_preferences(
)

try:
response = requests.post(
f"{settings.SEER_AUTOFIX_URL}{path}",
data=body,
headers={
"content-type": "application/json;charset=utf-8",
**sign_with_seer_secret(body),
},
response = make_signed_seer_api_request(
seer_autofix_default_connection_pool,
path,
body,
)
response.raise_for_status()
if response.status >= 400:
raise SeerApiError("Seer request failed", response.status)
logger.info(
"cleanup_seer_repository_preferences.success",
extra={
Expand Down
24 changes: 12 additions & 12 deletions src/sentry/tasks/seer_explorer_index.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,18 @@
from datetime import datetime, timedelta

import orjson
import requests
import sentry_sdk
from django.conf import settings
from django.utils import timezone as django_timezone

from sentry import features, options
from sentry.constants import ObjectStatus
from sentry.models.project import Project
from sentry.options.rollout import in_rollout_group
from sentry.seer.signed_seer_api import sign_with_seer_secret
from sentry.seer.models import SeerApiError
from sentry.seer.signed_seer_api import (
make_signed_seer_api_request,
seer_autofix_default_connection_pool,
)
from sentry.tasks.base import instrumented_task
from sentry.tasks.statistical_detectors import compute_delay
from sentry.taskworker.namespaces import seer_tasks
Expand Down Expand Up @@ -227,17 +229,15 @@ def run_explorer_index_for_projects(
path = "/v1/automation/explorer/index"

try:
response = requests.post(
f"{settings.SEER_AUTOFIX_URL}{path}",
data=body,
headers={
"content-type": "application/json;charset=utf-8",
**sign_with_seer_secret(body),
},
response = make_signed_seer_api_request(
seer_autofix_default_connection_pool,
path,
body,
timeout=30,
)
response.raise_for_status()
except requests.RequestException as e:
if response.status >= 400:
raise SeerApiError("Seer request failed", response.status)
except Exception as e:
logger.exception(
"Failed to schedule explorer index tasks in seer",
extra={
Expand Down
28 changes: 15 additions & 13 deletions src/sentry/uptime/seer_assertions.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,15 @@
from typing import Any

import orjson
import requests
from django.conf import settings
from pydantic import BaseModel, Field

from sentry.seer.signed_seer_api import sign_with_seer_secret
from urllib3.exceptions import MaxRetryError
from urllib3.exceptions import TimeoutError as Urllib3TimeoutError

from sentry.seer.models import SeerApiError
from sentry.seer.signed_seer_api import (
make_signed_seer_api_request,
seer_autofix_default_connection_pool,
)
from sentry.utils import json

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -351,17 +355,15 @@ def generate_assertion_suggestions(
)

try:
response = requests.post(
f"{settings.SEER_AUTOFIX_URL}/v1/llm/generate",
data=body,
headers={
"content-type": "application/json;charset=utf-8",
**sign_with_seer_secret(body),
},
response = make_signed_seer_api_request(
seer_autofix_default_connection_pool,
"/v1/llm/generate",
body,
timeout=30,
)
response.raise_for_status()
except requests.RequestException as e:
if response.status >= 400:
raise SeerApiError("Seer request failed", response.status)
except (SeerApiError, MaxRetryError, Urllib3TimeoutError) as e:
logger.exception("Failed to call Seer LLM proxy")
return None, f"Seer LLM proxy request failed: {e}"

Expand Down
Loading
Loading