Skip to content
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
34f6454
fix(mcp): Add None checks
alexander-alderman-webb May 7, 2026
d4028a6
update
alexander-alderman-webb May 7, 2026
a6d1fae
.
alexander-alderman-webb May 7, 2026
b1c2a7e
update args assignment
alexander-alderman-webb May 7, 2026
4b35081
remove type ignore
alexander-alderman-webb May 7, 2026
008c296
fix(opentelemetry): Handle ImportError
alexander-alderman-webb May 7, 2026
e4a76b7
ci: Remove unneeded type ignores and duplicate declarations
alexander-alderman-webb May 7, 2026
fb509be
.
alexander-alderman-webb May 7, 2026
e956503
.
alexander-alderman-webb May 7, 2026
64ef2d6
add type ignores
alexander-alderman-webb May 7, 2026
4ffcd06
ci(cloud_resource_context): Avoid not indexable mypy errors
alexander-alderman-webb May 7, 2026
9b6efcf
ci(grpc): Do not re-declare parameter
alexander-alderman-webb May 7, 2026
c3bc17b
ci(sanic): Widen type annotation
alexander-alderman-webb May 7, 2026
bda7d82
ci(starlette): Add type ignore
alexander-alderman-webb May 7, 2026
408255f
fix(wsgi): Do not catch None
alexander-alderman-webb May 7, 2026
0cbf62f
update
alexander-alderman-webb May 7, 2026
8d50aef
.
alexander-alderman-webb May 7, 2026
58e2860
Merge branch 'webb/starlette/mypy' into webb/wsgi-common/mypy
alexander-alderman-webb May 7, 2026
d02b641
Merge branch 'webb/mcp-mypy' into webb/wsgi-common/mypy
alexander-alderman-webb May 7, 2026
deb710e
resolve conflits
alexander-alderman-webb May 7, 2026
8ba3b49
Merge branch 'webb/starlette/mypy' into webb/wsgi-common/mypy
alexander-alderman-webb May 7, 2026
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
2 changes: 1 addition & 1 deletion sentry_sdk/_compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ def enabled(option: str) -> bool:
except Exception:
pass

return value and str(value).lower() not in FALSE_VALUES
return value and str(value).lower() not in FALSE_VALUES # type: ignore

# When `threads` is passed in as a uwsgi option,
# `enable-threads` is implied on.
Expand Down
8 changes: 4 additions & 4 deletions sentry_sdk/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -864,7 +864,7 @@ def capture_event(

:returns: An event ID. May be `None` if there is no DSN set or of if the SDK decided to discard the event for other reasons. In such situations setting `debug=True` on `init()` may help.
"""
hint: "Hint" = dict(hint or ())
hint = dict(hint or ())

if not self._should_capture(event, hint, scope):
return None
Expand Down Expand Up @@ -948,7 +948,7 @@ def _capture_telemetry(
if ty == "log":
before_send = get_before_send_log(self.options)
elif ty == "metric":
before_send = get_before_send_metric(self.options) # type: ignore
before_send = get_before_send_metric(self.options)

if before_send is not None:
telemetry = before_send(telemetry, {}) # type: ignore
Expand All @@ -960,9 +960,9 @@ def _capture_telemetry(
if ty == "log":
batcher = self.log_batcher
elif ty == "metric":
batcher = self.metrics_batcher # type: ignore
batcher = self.metrics_batcher
elif ty == "span":
batcher = self.span_batcher # type: ignore
batcher = self.span_batcher

if batcher is not None:
batcher.add(telemetry) # type: ignore
Expand Down
2 changes: 1 addition & 1 deletion sentry_sdk/crons/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ def _create_check_in_event(
monitor_config: "Optional[MonitorConfig]" = None,
) -> "Event":
options = sentry_sdk.get_client().options
check_in_id: str = check_in_id or uuid.uuid4().hex
check_in_id = check_in_id or uuid.uuid4().hex

check_in: "Event" = {
"type": "check_in",
Expand Down
2 changes: 1 addition & 1 deletion sentry_sdk/hub.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ def __init__(
scope = get_isolation_scope().fork()
current_scope = get_current_scope().fork()
else:
client = client_or_hub # type: ignore
client = client_or_hub
get_global_scope().set_client(client)

if scope is None: # so there is no Hub cloning going on
Expand Down
7 changes: 5 additions & 2 deletions sentry_sdk/integrations/_wsgi_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,11 @@

try:
from django.http.request import RawPostDataException

_RAW_DATA_EXCEPTIONS = (RawPostDataException, ValueError)
except ImportError:
RawPostDataException = None
_RAW_DATA_EXCEPTIONS = (ValueError,)

from typing import TYPE_CHECKING

Expand Down Expand Up @@ -110,7 +113,7 @@ def extract_into_event(self, event: "Event") -> None:
raw_data = None
try:
raw_data = self.raw_data()
except (RawPostDataException, ValueError):
except _RAW_DATA_EXCEPTIONS:
# If DjangoRestFramework is used it already read the body for us
# so reading it here will fail. We can ignore this.
pass
Comment on lines 113 to 119
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Bug: In non-Django environments, including RawPostDataException (which is None) in an except block will raise a TypeError when a ValueError occurs, preventing proper exception handling.
Severity: MEDIUM

Suggested Fix

Conditionally construct the tuple of exceptions to catch. Create a list containing ValueError, and only append RawPostDataException to it if RawPostDataException is not None. Then, convert the list to a tuple and use it in the except statement. This ensures None is never part of the exception handler.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.

Location: sentry_sdk/integrations/_wsgi_common.py#L113-L119

Potential issue: In environments where Django is not installed, `RawPostDataException`
is initialized to `None`. The `try...except` block at
`sentry_sdk/integrations/_wsgi_common.py:113` attempts to catch a tuple of exceptions
`(RawPostDataException, ValueError)`. When `RawPostDataException` is `None`, this
evaluates to `(None, ValueError)`. Python does not allow non-exception types like `None`
in an `except` clause, so if `self.raw_data()` raises a `ValueError` (an expected
scenario), the program will raise a `TypeError` instead of catching the `ValueError`.
This prevents the intended error handling from running and causes an unhandled
`TypeError` to propagate.

Did we get this right? 👍 / 👎 to inform future reviews.

Expand Down Expand Up @@ -175,7 +178,7 @@ def json(self) -> "Optional[Any]":

try:
raw_data = self.raw_data()
except (RawPostDataException, ValueError):
except _RAW_DATA_EXCEPTIONS:
# The body might have already been read, in which case this will
# fail
raw_data = None
Expand Down
18 changes: 9 additions & 9 deletions sentry_sdk/integrations/cloud_resource_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ def _get_gcp_context(cls) -> "Dict[str, str]":
"cloud.platform": CLOUD_PLATFORM.GCP_COMPUTE_ENGINE,
}

gcp_metadata = cls.gcp_metadata
try:
if cls.gcp_metadata is None:
r = cls.http.request(
Expand All @@ -187,30 +188,29 @@ def _get_gcp_context(cls) -> "Dict[str, str]":
if r.status != 200:
return ctx

cls.gcp_metadata = json.loads(r.data.decode("utf-8"))
gcp_metadata = json.loads(r.data.decode("utf-8"))
cls.gcp_metadata = gcp_metadata

try:
ctx["cloud.account.id"] = cls.gcp_metadata["project"]["projectId"]
ctx["cloud.account.id"] = gcp_metadata["project"]["projectId"]
except Exception:
pass

try:
ctx["cloud.availability_zone"] = cls.gcp_metadata["instance"][
"zone"
].split("/")[-1]
ctx["cloud.availability_zone"] = gcp_metadata["instance"]["zone"].split(
"/"
)[-1]
except Exception:
pass

try:
# only populated in google cloud run
ctx["cloud.region"] = cls.gcp_metadata["instance"]["region"].split("/")[
-1
]
ctx["cloud.region"] = gcp_metadata["instance"]["region"].split("/")[-1]
except Exception:
pass

try:
ctx["host.id"] = cls.gcp_metadata["instance"]["id"]
ctx["host.id"] = gcp_metadata["instance"]["id"]
except Exception:
pass

Expand Down
2 changes: 1 addition & 1 deletion sentry_sdk/integrations/dramatiq.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ def sentry_patched_broker__init__(
# RedisBroker does not.
if len(args) == 1:
middleware = args[0]
args = [] # type: ignore
args = ()
else:
middleware = None

Expand Down
2 changes: 1 addition & 1 deletion sentry_sdk/integrations/grpc/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ def patched_aio_server( # type: ignore
**kwargs: "P.kwargs",
) -> "Server":
server_interceptor = AsyncServerInterceptor()
interceptors: "Sequence[grpc.ServerInterceptor]" = [
interceptors = [
server_interceptor,
*(interceptors or []),
]
Expand Down
6 changes: 3 additions & 3 deletions sentry_sdk/integrations/mcp.py
Original file line number Diff line number Diff line change
Expand Up @@ -495,7 +495,7 @@ async def _handler_wrapper(
uri = original_kwargs.get("uri")

protocol = None
if hasattr(uri, "scheme"):
if uri is not None and hasattr(uri, "scheme"):
Comment thread
alexander-alderman-webb marked this conversation as resolved.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Idk if scheme can ever be None but this would be equivalent to the previous check:

Suggested change
if uri is not None and hasattr(uri, "scheme"):
if uri is not None and getattr(uri, "scheme", None):

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I'll address on #6218

protocol = uri.scheme
elif handler_name and "://" in handler_name:
protocol = handler_name.split("://")[0]
Expand Down Expand Up @@ -638,7 +638,7 @@ def _patch_fastmcp() -> None:
This function patches the _get_prompt_mcp and _read_resource_mcp methods
to add instrumentation for those handlers.
"""
if hasattr(FastMCP, "_get_prompt_mcp"):
if FastMCP is not None and hasattr(FastMCP, "_get_prompt_mcp"):
original_get_prompt_mcp = FastMCP._get_prompt_mcp

@wraps(original_get_prompt_mcp)
Expand All @@ -655,7 +655,7 @@ async def patched_get_prompt_mcp(

FastMCP._get_prompt_mcp = patched_get_prompt_mcp

if hasattr(FastMCP, "_read_resource_mcp"):
if FastMCP is not None and hasattr(FastMCP, "_read_resource_mcp"):
original_read_resource_mcp = FastMCP._read_resource_mcp

@wraps(original_read_resource_mcp)
Expand Down
2 changes: 2 additions & 0 deletions sentry_sdk/integrations/opentelemetry/integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,6 @@ def _setup_sentry_tracing() -> None:

def _setup_instrumentors() -> None:
for instrumentor, kwargs in CONFIGURABLE_INSTRUMENTATIONS.items():
if instrumentor is None:
continue
instrumentor().instrument(**kwargs)
2 changes: 1 addition & 1 deletion sentry_sdk/integrations/sanic.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
class SanicIntegration(Integration):
identifier = "sanic"
origin = f"auto.http.{identifier}"
version = None
version: "Optional[tuple[int, ...]]" = None

def __init__(
self, unsampled_statuses: "Optional[Container[int]]" = frozenset({404})
Expand Down
2 changes: 1 addition & 1 deletion sentry_sdk/integrations/starlette.py
Original file line number Diff line number Diff line change
Expand Up @@ -475,7 +475,7 @@ def _is_async_callable(obj: "Any") -> bool:
obj = obj.func

return iscoroutinefunction(obj) or (
callable(obj) and iscoroutinefunction(obj.__call__)
callable(obj) and iscoroutinefunction(obj.__call__) # type: ignore[operator]
)


Expand Down
6 changes: 3 additions & 3 deletions sentry_sdk/scope.py
Original file line number Diff line number Diff line change
Expand Up @@ -518,7 +518,7 @@ def _load_trace_data_from_env(self) -> "Optional[Dict[str, str]]":
Load Sentry trace id and baggage from environment variables.
Can be disabled by setting SENTRY_USE_ENVIRONMENT to "false".
"""
incoming_trace_information = None
incoming_trace_information: "Optional[Dict[str, str]]" = None

sentry_use_environment = (
os.environ.get("SENTRY_USE_ENVIRONMENT") or ""
Expand Down Expand Up @@ -1065,12 +1065,12 @@ def add_breadcrumb(
before_breadcrumb = client.options.get("before_breadcrumb")
max_breadcrumbs = client.options.get("max_breadcrumbs", DEFAULT_MAX_BREADCRUMBS)

crumb: "Breadcrumb" = dict(crumb or ())
crumb = dict(crumb or ())
crumb.update(kwargs)
if not crumb:
return

hint: "Hint" = dict(hint or ())
hint = dict(hint or ())

if crumb.get("timestamp") is None:
crumb["timestamp"] = datetime.now(timezone.utc)
Expand Down
4 changes: 2 additions & 2 deletions sentry_sdk/serializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -338,9 +338,9 @@ def _serialize_node_impl(
):
rv_list = []

for i, v in enumerate(obj):
for i, v in enumerate(obj): # type: ignore
if remaining_breadth is not None and i >= remaining_breadth:
_annotate(len=len(obj))
_annotate(len=len(obj)) # type: ignore
break

rv_list.append(
Expand Down
4 changes: 1 addition & 3 deletions sentry_sdk/tracing.py
Original file line number Diff line number Diff line change
Expand Up @@ -965,9 +965,7 @@ def finish(

# For backwards compatibility, we must handle the case where `scope`
# or `hub` could both either be a `Scope` or a `Hub`.
scope: "Optional[sentry_sdk.Scope]" = self._get_scope_from_finish_args(
scope, hub
)
scope = self._get_scope_from_finish_args(scope, hub)

scope = scope or self.scope or sentry_sdk.get_current_scope()
client = sentry_sdk.get_client()
Expand Down
Loading