ci: add strict-no-cover to detect unnecessary coverage pragmas (#1897)
This commit is contained in:
parent
3cfdea0e97
commit
a7ddfdae07
12 changed files with 355 additions and 358 deletions
|
|
@ -178,7 +178,7 @@ class TokenHandler:
|
|||
except TokenError as e:
|
||||
return self.response(TokenErrorResponse(error=e.error, error_description=e.error_description))
|
||||
|
||||
case RefreshTokenRequest(): # pragma: no cover
|
||||
case RefreshTokenRequest(): # pragma: no branch
|
||||
refresh_token = await self.provider.load_refresh_token(client_info, token_request.refresh_token)
|
||||
if refresh_token is None or refresh_token.client_id != token_request.client_id:
|
||||
# if token belongs to different client, pretend it doesn't exist
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ from mcp.shared.auth import OAuthClientInformationFull
|
|||
|
||||
class AuthenticationError(Exception):
|
||||
def __init__(self, message: str):
|
||||
self.message = message # pragma: no cover
|
||||
self.message = message
|
||||
|
||||
|
||||
class ClientAuthenticator:
|
||||
|
|
@ -96,15 +96,15 @@ class ClientAuthenticator:
|
|||
|
||||
# If client from the store expects a secret, validate that the request provides
|
||||
# that secret
|
||||
if client.client_secret: # pragma: no branch
|
||||
if client.client_secret:
|
||||
if not request_client_secret:
|
||||
raise AuthenticationError("Client secret is required") # pragma: no cover
|
||||
raise AuthenticationError("Client secret is required")
|
||||
|
||||
# hmac.compare_digest requires that both arguments are either bytes or a `str` containing
|
||||
# only ASCII characters. Since we do not control `request_client_secret`, we encode both
|
||||
# arguments to bytes.
|
||||
if not hmac.compare_digest(client.client_secret.encode(), request_client_secret.encode()):
|
||||
raise AuthenticationError("Invalid client_secret") # pragma: no cover
|
||||
raise AuthenticationError("Invalid client_secret")
|
||||
|
||||
if client.client_secret_expires_at and client.client_secret_expires_at < int(time.time()):
|
||||
raise AuthenticationError("Client secret has expired") # pragma: no cover
|
||||
|
|
|
|||
|
|
@ -247,8 +247,7 @@ class ServerTaskContext:
|
|||
response_data = await resolver.wait()
|
||||
await self._store.update_task(self.task_id, status=TASK_STATUS_WORKING)
|
||||
return ElicitResult.model_validate(response_data)
|
||||
except anyio.get_cancelled_exc_class(): # pragma: no cover
|
||||
# Coverage can't track async exception handlers reliably.
|
||||
except anyio.get_cancelled_exc_class():
|
||||
# This path is tested in test_elicit_restores_status_on_cancellation
|
||||
# which verifies status is restored to "working" after cancellation.
|
||||
await self._store.update_task(self.task_id, status=TASK_STATUS_WORKING)
|
||||
|
|
@ -408,8 +407,7 @@ class ServerTaskContext:
|
|||
response_data = await resolver.wait()
|
||||
await self._store.update_task(self.task_id, status=TASK_STATUS_WORKING)
|
||||
return CreateMessageResult.model_validate(response_data)
|
||||
except anyio.get_cancelled_exc_class(): # pragma: no cover
|
||||
# Coverage can't track async exception handlers reliably.
|
||||
except anyio.get_cancelled_exc_class():
|
||||
# This path is tested in test_create_message_restores_status_on_cancellation
|
||||
# which verifies status is restored to "working" after cancellation.
|
||||
await self._store.update_task(self.task_id, status=TASK_STATUS_WORKING)
|
||||
|
|
|
|||
|
|
@ -124,7 +124,7 @@ class FileResource(Resource):
|
|||
|
||||
@pydantic.field_validator("path")
|
||||
@classmethod
|
||||
def validate_absolute_path(cls, path: Path) -> Path: # pragma: no cover
|
||||
def validate_absolute_path(cls, path: Path) -> Path:
|
||||
"""Ensure path is absolute."""
|
||||
if not path.is_absolute():
|
||||
raise ValueError("Path must be absolute")
|
||||
|
|
|
|||
|
|
@ -873,10 +873,10 @@ class FastMCP(Generic[LifespanResultT]):
|
|||
app=RequireAuthMiddleware(sse.handle_post_message, required_scopes, resource_metadata_url),
|
||||
)
|
||||
)
|
||||
else: # pragma: no cover
|
||||
else:
|
||||
# Auth is disabled, no need for RequireAuthMiddleware
|
||||
# Since handle_sse is an ASGI app, we need to create a compatible endpoint
|
||||
async def sse_endpoint(request: Request) -> Response:
|
||||
async def sse_endpoint(request: Request) -> Response: # pragma: no cover
|
||||
# Convert the Starlette request to ASGI parameters
|
||||
return await handle_sse(request.scope, request.receive, request._send) # type: ignore[reportPrivateUsage]
|
||||
|
||||
|
|
|
|||
|
|
@ -118,7 +118,7 @@ class Tool(BaseModel):
|
|||
|
||||
|
||||
def _is_async_callable(obj: Any) -> bool:
|
||||
while isinstance(obj, functools.partial): # pragma: no cover
|
||||
while isinstance(obj, functools.partial): # pragma: lax no cover
|
||||
obj = obj.func
|
||||
|
||||
return inspect.iscoroutinefunction(obj) or (
|
||||
|
|
|
|||
|
|
@ -25,8 +25,7 @@ def find_context_parameter(fn: Callable[..., Any]) -> str | None:
|
|||
# Get type hints to properly resolve string annotations
|
||||
try:
|
||||
hints = typing.get_type_hints(fn)
|
||||
# TODO(Marcelo): Drop the pragma once https://github.com/coveragepy/coveragepy/issues/1987 is fixed.
|
||||
except Exception: # pragma: no cover
|
||||
except Exception: # pragma: lax no cover
|
||||
# If we can't resolve type hints, we can't find the context parameter
|
||||
return None
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ def configure_logging(
|
|||
level: the log level to use
|
||||
"""
|
||||
handlers: list[logging.Handler] = []
|
||||
try: # pragma: no cover
|
||||
try:
|
||||
from rich.console import Console
|
||||
from rich.logging import RichHandler
|
||||
|
||||
|
|
|
|||
|
|
@ -233,7 +233,7 @@ class Server(Generic[LifespanResultT, RequestT]):
|
|||
tools_capability = types.ToolsCapability(list_changed=notification_options.tools_changed)
|
||||
|
||||
# Set logging capabilities if handler exists
|
||||
if types.SetLevelRequest in self.request_handlers: # pragma: no cover
|
||||
if types.SetLevelRequest in self.request_handlers:
|
||||
logging_capability = types.LoggingCapability()
|
||||
|
||||
# Set completions capabilities if handler exists
|
||||
|
|
@ -379,7 +379,7 @@ class Server(Generic[LifespanResultT, RequestT]):
|
|||
mime_type=mime_type or "text/plain",
|
||||
**meta_kwargs,
|
||||
)
|
||||
case bytes() as data: # pragma: no cover
|
||||
case bytes() as data: # pragma: no branch
|
||||
return types.BlobResourceContents(
|
||||
uri=req.params.uri,
|
||||
blob=base64.b64encode(data).decode(),
|
||||
|
|
@ -388,7 +388,7 @@ class Server(Generic[LifespanResultT, RequestT]):
|
|||
)
|
||||
|
||||
match result:
|
||||
case str() | bytes() as data: # pragma: no cover
|
||||
case str() | bytes() as data: # pragma: lax no cover
|
||||
warnings.warn(
|
||||
"Returning str or bytes from read_resource is deprecated. "
|
||||
"Use Iterable[ReadResourceContents] instead.",
|
||||
|
|
@ -416,7 +416,7 @@ class Server(Generic[LifespanResultT, RequestT]):
|
|||
|
||||
return decorator
|
||||
|
||||
def set_logging_level(self): # pragma: no cover
|
||||
def set_logging_level(self):
|
||||
def decorator(func: Callable[[types.LoggingLevel], Awaitable[None]]):
|
||||
logger.debug("Registering handler for SetLevelRequest")
|
||||
|
||||
|
|
@ -429,7 +429,7 @@ class Server(Generic[LifespanResultT, RequestT]):
|
|||
|
||||
return decorator
|
||||
|
||||
def subscribe_resource(self): # pragma: no cover
|
||||
def subscribe_resource(self):
|
||||
def decorator(func: Callable[[str], Awaitable[None]]):
|
||||
logger.debug("Registering handler for SubscribeRequest")
|
||||
|
||||
|
|
@ -442,7 +442,7 @@ class Server(Generic[LifespanResultT, RequestT]):
|
|||
|
||||
return decorator
|
||||
|
||||
def unsubscribe_resource(self): # pragma: no cover
|
||||
def unsubscribe_resource(self):
|
||||
def decorator(func: Callable[[str], Awaitable[None]]):
|
||||
logger.debug("Registering handler for UnsubscribeRequest")
|
||||
|
||||
|
|
@ -468,7 +468,7 @@ class Server(Generic[LifespanResultT, RequestT]):
|
|||
result = await wrapper(req)
|
||||
|
||||
# Handle both old style (list[Tool]) and new style (ListToolsResult)
|
||||
if isinstance(result, types.ListToolsResult): # pragma: no cover
|
||||
if isinstance(result, types.ListToolsResult):
|
||||
# Refresh the tool cache with returned tools
|
||||
for tool in result.tools:
|
||||
validate_and_warn_tool_name(tool.name)
|
||||
|
|
@ -571,7 +571,7 @@ class Server(Generic[LifespanResultT, RequestT]):
|
|||
# tool returned structured content only
|
||||
maybe_structured_content = cast(StructuredContent, results)
|
||||
unstructured_content = [types.TextContent(type="text", text=json.dumps(results, indent=2))]
|
||||
elif hasattr(results, "__iter__"): # pragma: no cover
|
||||
elif hasattr(results, "__iter__"):
|
||||
# tool returned unstructured content only
|
||||
unstructured_content = cast(UnstructuredContent, results)
|
||||
maybe_structured_content = None
|
||||
|
|
@ -714,7 +714,7 @@ class Server(Generic[LifespanResultT, RequestT]):
|
|||
await self._handle_request(
|
||||
message, responder.request, session, lifespan_context, raise_exceptions
|
||||
)
|
||||
case Exception(): # pragma: no cover
|
||||
case Exception():
|
||||
logger.error(f"Received exception from stream: {message}")
|
||||
await session.send_log_message(
|
||||
level="error",
|
||||
|
|
@ -726,7 +726,7 @@ class Server(Generic[LifespanResultT, RequestT]):
|
|||
case _:
|
||||
await self._handle_notification(message)
|
||||
|
||||
for warning in w: # pragma: no cover
|
||||
for warning in w: # pragma: lax no cover
|
||||
logger.info("Warning: %s: %s", warning.category.__name__, warning.message)
|
||||
|
||||
async def _handle_request(
|
||||
|
|
@ -781,16 +781,16 @@ class Server(Generic[LifespanResultT, RequestT]):
|
|||
)
|
||||
)
|
||||
response = await handler(req)
|
||||
except McpError as err: # pragma: no cover
|
||||
except McpError as err:
|
||||
response = err.error
|
||||
except anyio.get_cancelled_exc_class(): # pragma: no cover
|
||||
except anyio.get_cancelled_exc_class():
|
||||
logger.info(
|
||||
"Request %s cancelled - duplicate response suppressed",
|
||||
message.request_id,
|
||||
)
|
||||
return
|
||||
except Exception as err: # pragma: no cover
|
||||
if raise_exceptions:
|
||||
except Exception as err:
|
||||
if raise_exceptions: # pragma: no cover
|
||||
raise err
|
||||
response = types.ErrorData(code=0, message=str(err), data=None)
|
||||
finally:
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@ class ServerSession(
|
|||
|
||||
@property
|
||||
def client_params(self) -> types.InitializeRequestParams | None:
|
||||
return self._client_params # pragma: no cover
|
||||
return self._client_params
|
||||
|
||||
@property
|
||||
def experimental(self) -> ExperimentalServerSessionFeatures:
|
||||
|
|
@ -126,20 +126,20 @@ class ServerSession(
|
|||
self._experimental_features = ExperimentalServerSessionFeatures(self)
|
||||
return self._experimental_features
|
||||
|
||||
def check_client_capability(self, capability: types.ClientCapabilities) -> bool: # pragma: no cover
|
||||
def check_client_capability(self, capability: types.ClientCapabilities) -> bool:
|
||||
"""Check if the client supports a specific capability."""
|
||||
if self._client_params is None:
|
||||
if self._client_params is None: # pragma: lax no cover
|
||||
return False
|
||||
|
||||
client_caps = self._client_params.capabilities
|
||||
|
||||
if capability.roots is not None:
|
||||
if capability.roots is not None: # pragma: lax no cover
|
||||
if client_caps.roots is None:
|
||||
return False
|
||||
if capability.roots.list_changed and not client_caps.roots.list_changed:
|
||||
return False
|
||||
|
||||
if capability.sampling is not None:
|
||||
if capability.sampling is not None: # pragma: lax no cover
|
||||
if client_caps.sampling is None:
|
||||
return False
|
||||
if capability.sampling.context is not None and client_caps.sampling.context is None:
|
||||
|
|
@ -147,17 +147,17 @@ class ServerSession(
|
|||
if capability.sampling.tools is not None and client_caps.sampling.tools is None:
|
||||
return False
|
||||
|
||||
if capability.elicitation is not None and client_caps.elicitation is None:
|
||||
if capability.elicitation is not None and client_caps.elicitation is None: # pragma: lax no cover
|
||||
return False
|
||||
|
||||
if capability.experimental is not None:
|
||||
if capability.experimental is not None: # pragma: lax no cover
|
||||
if client_caps.experimental is None:
|
||||
return False
|
||||
for exp_key, exp_value in capability.experimental.items():
|
||||
if exp_key not in client_caps.experimental or client_caps.experimental[exp_key] != exp_value:
|
||||
return False
|
||||
|
||||
if capability.tasks is not None:
|
||||
if capability.tasks is not None: # pragma: lax no cover
|
||||
if client_caps.tasks is None:
|
||||
return False
|
||||
if not check_tasks_capability(capability.tasks, client_caps.tasks):
|
||||
|
|
@ -544,7 +544,7 @@ class ServerSession(
|
|||
# Add related-task metadata if associated with a parent task
|
||||
if related_task_id is not None:
|
||||
# Defensive: model_dump() never includes _meta, but guard against future changes
|
||||
if "_meta" not in params_data: # pragma: no cover
|
||||
if "_meta" not in params_data: # pragma: no branch
|
||||
params_data["_meta"] = {}
|
||||
params_data["_meta"][RELATED_TASK_METADATA_KEY] = types.RelatedTaskMetadata(
|
||||
task_id=related_task_id
|
||||
|
|
@ -589,7 +589,7 @@ class ServerSession(
|
|||
# Add related-task metadata if associated with a parent task
|
||||
if related_task_id is not None:
|
||||
# Defensive: model_dump() never includes _meta, but guard against future changes
|
||||
if "_meta" not in params_data: # pragma: no cover
|
||||
if "_meta" not in params_data: # pragma: no branch
|
||||
params_data["_meta"] = {}
|
||||
params_data["_meta"][RELATED_TASK_METADATA_KEY] = types.RelatedTaskMetadata(
|
||||
task_id=related_task_id
|
||||
|
|
@ -659,7 +659,7 @@ class ServerSession(
|
|||
# Add related-task metadata if associated with a parent task
|
||||
if related_task_id is not None:
|
||||
# Defensive: model_dump() never includes _meta, but guard against future changes
|
||||
if "_meta" not in params_data: # pragma: no cover
|
||||
if "_meta" not in params_data: # pragma: no branch
|
||||
params_data["_meta"] = {}
|
||||
params_data["_meta"][RELATED_TASK_METADATA_KEY] = types.RelatedTaskMetadata(
|
||||
task_id=related_task_id
|
||||
|
|
|
|||
|
|
@ -222,7 +222,7 @@ class StreamableHTTPServerTransport:
|
|||
"""
|
||||
self.close_sse_stream(GET_STREAM_KEY)
|
||||
|
||||
def _create_session_message( # pragma: no cover
|
||||
def _create_session_message(
|
||||
self,
|
||||
message: JSONRPCMessage,
|
||||
request: Request,
|
||||
|
|
@ -238,10 +238,10 @@ class StreamableHTTPServerTransport:
|
|||
# Only provide close callbacks when client supports resumability
|
||||
if self._event_store and protocol_version >= "2025-11-25":
|
||||
|
||||
async def close_stream_callback() -> None:
|
||||
async def close_stream_callback() -> None: # pragma: no cover
|
||||
self.close_sse_stream(request_id)
|
||||
|
||||
async def close_standalone_stream_callback() -> None:
|
||||
async def close_standalone_stream_callback() -> None: # pragma: no cover
|
||||
self.close_standalone_sse_stream()
|
||||
|
||||
metadata = ServerMessageMetadata(
|
||||
|
|
@ -308,7 +308,7 @@ class StreamableHTTPServerTransport:
|
|||
headers=response_headers,
|
||||
)
|
||||
|
||||
def _create_json_response( # pragma: no cover
|
||||
def _create_json_response(
|
||||
self,
|
||||
response_message: JSONRPCMessage | None,
|
||||
status_code: HTTPStatus = HTTPStatus.OK,
|
||||
|
|
@ -316,10 +316,10 @@ class StreamableHTTPServerTransport:
|
|||
) -> Response:
|
||||
"""Create a JSON response from a JSONRPCMessage"""
|
||||
response_headers = {"Content-Type": CONTENT_TYPE_JSON}
|
||||
if headers:
|
||||
if headers: # pragma: lax no cover
|
||||
response_headers.update(headers)
|
||||
|
||||
if self.mcp_session_id:
|
||||
if self.mcp_session_id: # pragma: lax no cover
|
||||
response_headers[MCP_SESSION_ID_HEADER] = self.mcp_session_id
|
||||
|
||||
return Response(
|
||||
|
|
@ -345,14 +345,14 @@ class StreamableHTTPServerTransport:
|
|||
|
||||
return event_data
|
||||
|
||||
async def _clean_up_memory_streams(self, request_id: RequestId) -> None: # pragma: no cover
|
||||
async def _clean_up_memory_streams(self, request_id: RequestId) -> None:
|
||||
"""Clean up memory streams for a given request ID."""
|
||||
if request_id in self._request_streams:
|
||||
if request_id in self._request_streams: # pragma: no branch
|
||||
try:
|
||||
# Close the request stream
|
||||
await self._request_streams[request_id][0].aclose()
|
||||
await self._request_streams[request_id][1].aclose()
|
||||
except Exception:
|
||||
except Exception: # pragma: no cover
|
||||
# During cleanup, we catch all exceptions since streams might be in various states
|
||||
logger.debug("Error closing memory streams - may already be closed")
|
||||
finally:
|
||||
|
|
@ -366,7 +366,7 @@ class StreamableHTTPServerTransport:
|
|||
# Validate request headers for DNS rebinding protection
|
||||
is_post = request.method == "POST"
|
||||
error_response = await self._security.validate_request(request, is_post=is_post)
|
||||
if error_response: # pragma: no cover
|
||||
if error_response:
|
||||
await error_response(scope, receive, send)
|
||||
return
|
||||
|
||||
|
|
@ -405,12 +405,12 @@ class StreamableHTTPServerTransport:
|
|||
|
||||
return any(part == CONTENT_TYPE_JSON for part in content_type_parts)
|
||||
|
||||
async def _validate_accept_header(self, request: Request, scope: Scope, send: Send) -> bool: # pragma: no cover
|
||||
async def _validate_accept_header(self, request: Request, scope: Scope, send: Send) -> bool:
|
||||
"""Validate Accept header based on response mode. Returns True if valid."""
|
||||
has_json, has_sse = self._check_accept_headers(request)
|
||||
if self.is_json_response_enabled:
|
||||
# For JSON-only responses, only require application/json
|
||||
if not has_json:
|
||||
if not has_json: # pragma: lax no cover
|
||||
response = self._create_error_response(
|
||||
"Not Acceptable: Client must accept application/json",
|
||||
HTTPStatus.NOT_ACCEPTABLE,
|
||||
|
|
@ -456,7 +456,7 @@ class StreamableHTTPServerTransport:
|
|||
await response(scope, receive, send)
|
||||
return
|
||||
|
||||
try: # pragma: no cover
|
||||
try:
|
||||
message = jsonrpc_message_adapter.validate_python(raw_message, by_name=False)
|
||||
except ValidationError as e: # pragma: no cover
|
||||
response = self._create_error_response(
|
||||
|
|
@ -513,12 +513,12 @@ class StreamableHTTPServerTransport:
|
|||
)
|
||||
|
||||
# Extract the request ID outside the try block for proper scope
|
||||
request_id = str(message.id) # pragma: no cover
|
||||
request_id = str(message.id)
|
||||
# Register this stream for the request ID
|
||||
self._request_streams[request_id] = anyio.create_memory_object_stream[EventMessage](0) # pragma: no cover
|
||||
request_stream_reader = self._request_streams[request_id][1] # pragma: no cover
|
||||
self._request_streams[request_id] = anyio.create_memory_object_stream[EventMessage](0)
|
||||
request_stream_reader = self._request_streams[request_id][1]
|
||||
|
||||
if self.is_json_response_enabled: # pragma: no cover
|
||||
if self.is_json_response_enabled:
|
||||
# Process the message
|
||||
metadata = ServerMessageMetadata(request_context=request)
|
||||
session_message = SessionMessage(message, metadata=metadata)
|
||||
|
|
@ -529,13 +529,13 @@ class StreamableHTTPServerTransport:
|
|||
response_message = None
|
||||
|
||||
# Use similar approach to SSE writer for consistency
|
||||
async for event_message in request_stream_reader:
|
||||
async for event_message in request_stream_reader: # pragma: no branch
|
||||
# If it's a response, this is what we're waiting for
|
||||
if isinstance(event_message.message, JSONRPCResponse | JSONRPCError):
|
||||
response_message = event_message.message
|
||||
break
|
||||
# For notifications and request, keep waiting
|
||||
else:
|
||||
else: # pragma: no cover
|
||||
logger.debug(f"received: {event_message.message.method}")
|
||||
|
||||
# At this point we should have a response
|
||||
|
|
@ -543,7 +543,7 @@ class StreamableHTTPServerTransport:
|
|||
# Create JSON response
|
||||
response = self._create_json_response(response_message)
|
||||
await response(scope, receive, send)
|
||||
else:
|
||||
else: # pragma: no cover
|
||||
# This shouldn't happen in normal operation
|
||||
logger.error("No response message received before stream closed")
|
||||
response = self._create_error_response(
|
||||
|
|
@ -551,7 +551,7 @@ class StreamableHTTPServerTransport:
|
|||
HTTPStatus.INTERNAL_SERVER_ERROR,
|
||||
)
|
||||
await response(scope, receive, send)
|
||||
except Exception:
|
||||
except Exception: # pragma: no cover
|
||||
logger.exception("Error processing JSON response")
|
||||
response = self._create_error_response(
|
||||
"Error processing request",
|
||||
|
|
@ -762,7 +762,7 @@ class StreamableHTTPServerTransport:
|
|||
request_stream_keys = list(self._request_streams.keys())
|
||||
|
||||
# Close all request streams asynchronously
|
||||
for key in request_stream_keys: # pragma: no cover
|
||||
for key in request_stream_keys: # pragma: lax no cover
|
||||
await self._clean_up_memory_streams(key)
|
||||
|
||||
# Clear the request streams dictionary immediately
|
||||
|
|
@ -967,9 +967,9 @@ class StreamableHTTPServerTransport:
|
|||
# Start a task group for message routing
|
||||
async with anyio.create_task_group() as tg:
|
||||
# Create a message router that distributes messages to request streams
|
||||
async def message_router(): # pragma: no cover
|
||||
async def message_router():
|
||||
try:
|
||||
async for session_message in write_stream_reader:
|
||||
async for session_message in write_stream_reader: # pragma: no branch
|
||||
# Determine which request stream(s) should receive this message
|
||||
message = session_message.message
|
||||
target_request_id = None
|
||||
|
|
@ -980,7 +980,7 @@ class StreamableHTTPServerTransport:
|
|||
# send it there
|
||||
target_request_id = response_id
|
||||
# Extract related_request_id from meta if it exists
|
||||
elif (
|
||||
elif ( # pragma: no cover
|
||||
session_message.metadata is not None
|
||||
and isinstance(
|
||||
session_message.metadata,
|
||||
|
|
@ -996,7 +996,7 @@ class StreamableHTTPServerTransport:
|
|||
# regardless of whether a client is connected
|
||||
# messages will be replayed on the re-connect
|
||||
event_id = None
|
||||
if self._event_store:
|
||||
if self._event_store: # pragma: lax no cover
|
||||
event_id = await self._event_store.store_event(request_stream_id, message)
|
||||
logger.debug(f"Stored {event_id} from {request_stream_id}")
|
||||
|
||||
|
|
@ -1004,13 +1004,13 @@ class StreamableHTTPServerTransport:
|
|||
try:
|
||||
# Send both the message and the event ID
|
||||
await self._request_streams[request_stream_id][0].send(EventMessage(message, event_id))
|
||||
except (
|
||||
except ( # pragma: no cover
|
||||
anyio.BrokenResourceError,
|
||||
anyio.ClosedResourceError,
|
||||
):
|
||||
# Stream might be closed, remove from registry
|
||||
self._request_streams.pop(request_stream_id, None)
|
||||
else:
|
||||
else: # pragma: no cover
|
||||
logger.debug(
|
||||
f"""Request stream {request_stream_id} not found
|
||||
for message. Still processing message as the client
|
||||
|
|
@ -1021,7 +1021,7 @@ class StreamableHTTPServerTransport:
|
|||
logger.debug("Read stream closed by client")
|
||||
else:
|
||||
logger.exception("Unexpected closure of read stream in message router")
|
||||
except Exception:
|
||||
except Exception: # pragma: lax no cover
|
||||
logger.exception("Error in message router")
|
||||
|
||||
# Start the message router
|
||||
|
|
@ -1031,7 +1031,7 @@ class StreamableHTTPServerTransport:
|
|||
# Yield the streams for the caller to use
|
||||
yield read_stream, write_stream
|
||||
finally:
|
||||
for stream_id in list(self._request_streams.keys()): # pragma: no cover
|
||||
for stream_id in list(self._request_streams.keys()): # pragma: lax no cover
|
||||
await self._clean_up_memory_streams(stream_id)
|
||||
self._request_streams.clear()
|
||||
|
||||
|
|
|
|||
|
|
@ -86,9 +86,9 @@ class TransportSecurityMiddleware:
|
|||
logger.warning(f"Invalid Origin header: {origin}")
|
||||
return False
|
||||
|
||||
def _validate_content_type(self, content_type: str | None) -> bool: # pragma: no cover
|
||||
def _validate_content_type(self, content_type: str | None) -> bool:
|
||||
"""Validate the Content-Type header for POST requests."""
|
||||
if not content_type:
|
||||
if not content_type: # pragma: lax no cover
|
||||
logger.warning("Missing Content-Type header in POST request")
|
||||
return False
|
||||
|
||||
|
|
@ -107,7 +107,7 @@ class TransportSecurityMiddleware:
|
|||
# Always validate Content-Type for POST requests
|
||||
if is_post: # pragma: no branch
|
||||
content_type = request.headers.get("content-type")
|
||||
if not self._validate_content_type(content_type): # pragma: no cover
|
||||
if not self._validate_content_type(content_type):
|
||||
return Response("Invalid Content-Type header", status_code=400)
|
||||
|
||||
# Skip remaining validation if DNS rebinding protection is disabled
|
||||
|
|
|
|||
Loading…
Reference in a new issue