diff --git a/.github/workflows/shared.yml b/.github/workflows/shared.yml index 108e6c6676..72e4c253d4 100644 --- a/.github/workflows/shared.yml +++ b/.github/workflows/shared.yml @@ -62,6 +62,10 @@ jobs: uv run --frozen --no-sync coverage combine uv run --frozen --no-sync coverage report + - name: Check for unnecessary no cover pragmas + if: runner.os != 'Windows' + run: uv run --frozen --no-sync strict-no-cover + readme-snippets: runs-on: ubuntu-latest steps: diff --git a/pyproject.toml b/pyproject.toml index 4408f08b80..1b131b922a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -67,8 +67,9 @@ dev = [ "pytest-pretty>=1.2.0", "inline-snapshot>=0.23.0", "dirty-equals>=0.9.0", - "coverage[toml]>=7.13.1", + "coverage[toml]>=7.10.7,<=7.13", "pillow>=12.0", + "strict-no-cover", ] docs = [ "mkdocs>=1.6.1", @@ -164,6 +165,7 @@ members = ["examples/clients/*", "examples/servers/*", "examples/snippets"] [tool.uv.sources] mcp = { workspace = true } +strict-no-cover = { git = "https://github.com/pydantic/strict-no-cover" } [tool.pytest.ini_options] log_cli = true @@ -199,7 +201,6 @@ branch = true patch = ["subprocess"] concurrency = ["multiprocessing", "thread"] source = ["src", "tests"] -relative_files = true omit = [ "src/mcp/client/__main__.py", "src/mcp/server/__main__.py", @@ -216,6 +217,7 @@ ignore_errors = true precision = 2 exclude_lines = [ "pragma: no cover", + "pragma: lax no cover", "if TYPE_CHECKING:", "@overload", "raise NotImplementedError", diff --git a/src/mcp/cli/claude.py b/src/mcp/cli/claude.py index f2dc6888a1..7c2f650edd 100644 --- a/src/mcp/cli/claude.py +++ b/src/mcp/cli/claude.py @@ -72,7 +72,7 @@ def update_claude_config( ) config_file = config_dir / "claude_desktop_config.json" - if not config_file.exists(): # pragma: no cover + if not config_file.exists(): # pragma: lax no cover try: config_file.write_text("{}") except Exception: diff --git a/src/mcp/cli/cli.py b/src/mcp/cli/cli.py index c4cae0dce3..5e1e641ac4 100644 --- a/src/mcp/cli/cli.py +++ b/src/mcp/cli/cli.py @@ -77,7 +77,7 @@ def _build_uv_command( if with_packages: for pkg in with_packages: - if pkg: # pragma: no cover + if pkg: # pragma: no branch cmd.extend(["--with", pkg]) # Add mcp run command diff --git a/src/mcp/client/auth/oauth2.py b/src/mcp/client/auth/oauth2.py index 8e699b57a0..98df4d25d0 100644 --- a/src/mcp/client/auth/oauth2.py +++ b/src/mcp/client/auth/oauth2.py @@ -192,7 +192,7 @@ def prepare_token_auth( headers = {} # pragma: no cover if not self.client_info: - return data, headers # pragma: no cover + return data, headers auth_method = self.client_info.token_endpoint_auth_method @@ -418,7 +418,7 @@ async def _refresh_token(self) -> httpx.Request: raise OAuthTokenError("No client info available") # pragma: no cover if self.context.oauth_metadata and self.context.oauth_metadata.token_endpoint: - token_url = str(self.context.oauth_metadata.token_endpoint) # pragma: no cover + token_url = str(self.context.oauth_metadata.token_endpoint) else: auth_base_url = self.context.get_authorization_base_url(self.context.server_url) token_url = urljoin(auth_base_url, "/token") @@ -534,7 +534,7 @@ async def async_auth_flow(self, request: httpx.Request) -> AsyncGenerator[httpx. ) # Step 2: Discover OAuth Authorization Server Metadata (OASM) (with fallback for legacy servers) - for url in asm_discovery_urls: # pragma: no cover + for url in asm_discovery_urls: # pragma: no branch oauth_metadata_request = create_oauth_metadata_request(url) oauth_metadata_response = yield oauth_metadata_request diff --git a/src/mcp/client/session.py b/src/mcp/client/session.py index d5d4c86077..7098132b03 100644 --- a/src/mcp/client/session.py +++ b/src/mcp/client/session.py @@ -250,7 +250,7 @@ async def set_logging_level( meta: RequestParamsMeta | None = None, ) -> types.EmptyResult: """Send a logging/setLevel request.""" - return await self.send_request( # pragma: no cover + return await self.send_request( types.SetLevelRequest(params=types.SetLevelRequestParams(level=level, _meta=meta)), types.EmptyResult, ) @@ -285,14 +285,14 @@ async def read_resource(self, uri: str, *, meta: RequestParamsMeta | None = None async def subscribe_resource(self, uri: str, *, meta: RequestParamsMeta | None = None) -> types.EmptyResult: """Send a resources/subscribe request.""" - return await self.send_request( # pragma: no cover + return await self.send_request( types.SubscribeRequest(params=types.SubscribeRequestParams(uri=uri, _meta=meta)), types.EmptyResult, ) async def unsubscribe_resource(self, uri: str, *, meta: RequestParamsMeta | None = None) -> types.EmptyResult: """Send a resources/unsubscribe request.""" - return await self.send_request( # pragma: no cover + return await self.send_request( types.UnsubscribeRequest(params=types.UnsubscribeRequestParams(uri=uri, _meta=meta)), types.EmptyResult, ) @@ -344,7 +344,7 @@ async def _validate_tool_result(self, name: str, result: types.CallToolResult) - try: validate(result.structured_content, output_schema) except ValidationError as e: - raise RuntimeError(f"Invalid structured content returned by tool {name}: {e}") # pragma: no cover + raise RuntimeError(f"Invalid structured content returned by tool {name}: {e}") except SchemaError as e: # pragma: no cover raise RuntimeError(f"Invalid schema for tool {name}: {e}") # pragma: no cover diff --git a/src/mcp/client/session_group.py b/src/mcp/client/session_group.py index 825e6d66e0..10ecdff0fb 100644 --- a/src/mcp/client/session_group.py +++ b/src/mcp/client/session_group.py @@ -223,22 +223,22 @@ async def disconnect_from_server(self, session: mcp.ClientSession) -> None: ) ) - if session_known_for_components: # pragma: no cover + if session_known_for_components: # pragma: no branch component_names = self._sessions.pop(session) # Pop from _sessions tracking # Remove prompts associated with the session. for name in component_names.prompts: - if name in self._prompts: + if name in self._prompts: # pragma: no branch del self._prompts[name] # Remove resources associated with the session. for name in component_names.resources: - if name in self._resources: + if name in self._resources: # pragma: no branch del self._resources[name] # Remove tools associated with the session. for name in component_names.tools: - if name in self._tools: + if name in self._tools: # pragma: no branch del self._tools[name] - if name in self._tool_to_session: + if name in self._tool_to_session: # pragma: no branch del self._tool_to_session[name] # Clean up the session's resources via its dedicated exit stack diff --git a/src/mcp/client/sse.py b/src/mcp/client/sse.py index 47e5b845a9..208427c436 100644 --- a/src/mcp/client/sse.py +++ b/src/mcp/client/sse.py @@ -119,12 +119,12 @@ async def sse_reader(task_status: TaskStatus[str] = anyio.TASK_STATUS_IGNORED): await read_stream_writer.send(session_message) case _: # pragma: no cover logger.warning(f"Unknown SSE event: {sse.event}") # pragma: no cover - except SSEError as sse_exc: # pragma: no cover - logger.exception("Encountered SSE exception") # pragma: no cover - raise sse_exc # pragma: no cover - except Exception as exc: # pragma: no cover - logger.exception("Error in sse_reader") # pragma: no cover - await read_stream_writer.send(exc) # pragma: no cover + except SSEError as sse_exc: # pragma: lax no cover + logger.exception("Encountered SSE exception") + raise sse_exc + except Exception as exc: # pragma: lax no cover + logger.exception("Error in sse_reader") + await read_stream_writer.send(exc) finally: await read_stream_writer.aclose() @@ -143,8 +143,8 @@ async def post_writer(endpoint_url: str): ) response.raise_for_status() logger.debug(f"Client message sent successfully: {response.status_code}") - except Exception: # pragma: no cover - logger.exception("Error in post_writer") # pragma: no cover + except Exception: # pragma: lax no cover + logger.exception("Error in post_writer") finally: await write_stream.aclose() diff --git a/src/mcp/client/stdio/__init__.py b/src/mcp/client/stdio/__init__.py index 19fdec5a38..1eeb6af8bc 100644 --- a/src/mcp/client/stdio/__init__.py +++ b/src/mcp/client/stdio/__init__.py @@ -57,7 +57,7 @@ def get_default_environment() -> dict[str, str]: for key in DEFAULT_INHERITED_ENV_VARS: value = os.environ.get(key) if value is None: - continue # pragma: no cover + continue if value.startswith("()"): # pragma: no cover # Skip functions, which are a security risk @@ -158,7 +158,7 @@ async def stdout_reader(): session_message = SessionMessage(message) await read_stream_writer.send(session_message) - except anyio.ClosedResourceError: # pragma: no cover + except anyio.ClosedResourceError: await anyio.lowlevel.checkpoint() async def stdin_writer(): @@ -226,7 +226,7 @@ def _get_executable_command(command: str) -> str: if sys.platform == "win32": # pragma: no cover return get_windows_executable_command(command) else: - return command # pragma: no cover + return command async def _create_platform_compatible_process( @@ -250,7 +250,7 @@ async def _create_platform_compatible_process( stderr=errlog, cwd=cwd, start_new_session=True, - ) # pragma: no cover + ) return process @@ -267,7 +267,7 @@ async def _terminate_process_tree(process: Process | FallbackProcess, timeout_se """ if sys.platform == "win32": # pragma: no cover await terminate_windows_process_tree(process, timeout_seconds) - else: # pragma: no cover + else: # FallbackProcess should only be used for Windows compatibility assert isinstance(process, Process) await terminate_posix_process_tree(process, timeout_seconds) diff --git a/src/mcp/client/streamable_http.py b/src/mcp/client/streamable_http.py index 555dd1290c..b573c2c5b8 100644 --- a/src/mcp/client/streamable_http.py +++ b/src/mcp/client/streamable_http.py @@ -181,7 +181,7 @@ async def handle_get_stream(self, client: httpx.AsyncClient, read_stream_writer: headers = self._prepare_headers() if last_event_id: - headers[LAST_EVENT_ID] = last_event_id # pragma: no cover + headers[LAST_EVENT_ID] = last_event_id async with aconnect_sse(client, "GET", self.url, headers=headers) as event_source: event_source.response.raise_for_status() @@ -190,19 +190,19 @@ async def handle_get_stream(self, client: httpx.AsyncClient, read_stream_writer: async for sse in event_source.aiter_sse(): # Track last event ID for reconnection if sse.id: - last_event_id = sse.id # pragma: no cover + last_event_id = sse.id # Track retry interval from server if sse.retry is not None: - retry_interval_ms = sse.retry # pragma: no cover + retry_interval_ms = sse.retry await self._handle_sse_event(sse, read_stream_writer) # Stream ended normally (server closed) - reset attempt counter attempt = 0 - except Exception as exc: # pragma: no cover - logger.debug(f"GET stream error: {exc}") - attempt += 1 + except Exception as exc: + logger.debug(f"GET stream error: {exc}") # pragma: no cover + attempt += 1 # pragma: no cover if attempt >= MAX_RECONNECTION_ATTEMPTS: # pragma: no cover logger.debug(f"GET stream max reconnection attempts ({MAX_RECONNECTION_ATTEMPTS}) exceeded") @@ -333,8 +333,8 @@ async def _handle_sse_response( if is_complete: await response.aclose() return # Normal completion, no reconnect needed - except Exception as e: # pragma: no cover - logger.debug(f"SSE stream ended: {e}") + except Exception as e: + logger.debug(f"SSE stream ended: {e}") # pragma: no cover # Stream ended without response - reconnect if we received an event with ID if last_event_id is not None: # pragma: no branch @@ -472,20 +472,20 @@ async def handle_request_async(): await read_stream_writer.aclose() await write_stream.aclose() - async def terminate_session(self, client: httpx.AsyncClient) -> None: # pragma: no cover + async def terminate_session(self, client: httpx.AsyncClient) -> None: """Terminate the session by sending a DELETE request.""" - if not self.session_id: + if not self.session_id: # pragma: lax no cover return try: headers = self._prepare_headers() response = await client.delete(self.url, headers=headers) - if response.status_code == 405: + if response.status_code == 405: # pragma: lax no cover logger.debug("Server does not allow session termination") - elif response.status_code not in (200, 204): + elif response.status_code not in (200, 204): # pragma: lax no cover logger.warning(f"Session termination failed: {response.status_code}") - except Exception as exc: + except Exception as exc: # pragma: no cover logger.warning(f"Session termination failed: {exc}") def get_session_id(self) -> str | None: diff --git a/src/mcp/server/auth/handlers/token.py b/src/mcp/server/auth/handlers/token.py index 14f6f68720..534a478a91 100644 --- a/src/mcp/server/auth/handlers/token.py +++ b/src/mcp/server/auth/handlers/token.py @@ -178,7 +178,7 @@ async def handle(self, request: Request): 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 diff --git a/src/mcp/server/auth/middleware/client_auth.py b/src/mcp/server/auth/middleware/client_auth.py index 4e4d9be2f6..8a6a1b5188 100644 --- a/src/mcp/server/auth/middleware/client_auth.py +++ b/src/mcp/server/auth/middleware/client_auth.py @@ -13,7 +13,7 @@ class AuthenticationError(Exception): def __init__(self, message: str): - self.message = message # pragma: no cover + self.message = message class ClientAuthenticator: @@ -96,15 +96,15 @@ async def authenticate_request(self, request: Request) -> OAuthClientInformation # 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 diff --git a/src/mcp/server/experimental/task_context.py b/src/mcp/server/experimental/task_context.py index 871cefd9f5..32394d0ad0 100644 --- a/src/mcp/server/experimental/task_context.py +++ b/src/mcp/server/experimental/task_context.py @@ -247,8 +247,7 @@ async def elicit( 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 @@ async def create_message( 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) diff --git a/src/mcp/server/fastmcp/resources/types.py b/src/mcp/server/fastmcp/resources/types.py index 791442f87e..683886fd8b 100644 --- a/src/mcp/server/fastmcp/resources/types.py +++ b/src/mcp/server/fastmcp/resources/types.py @@ -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") diff --git a/src/mcp/server/fastmcp/server.py b/src/mcp/server/fastmcp/server.py index d0a550280e..71cd81eb27 100644 --- a/src/mcp/server/fastmcp/server.py +++ b/src/mcp/server/fastmcp/server.py @@ -873,10 +873,10 @@ async def handle_sse(scope: Scope, receive: Receive, send: Send): # pragma: no 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] diff --git a/src/mcp/server/fastmcp/tools/base.py b/src/mcp/server/fastmcp/tools/base.py index b784d0f53d..e6a64f4d96 100644 --- a/src/mcp/server/fastmcp/tools/base.py +++ b/src/mcp/server/fastmcp/tools/base.py @@ -118,7 +118,7 @@ async def run( 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 ( diff --git a/src/mcp/server/fastmcp/utilities/context_injection.py b/src/mcp/server/fastmcp/utilities/context_injection.py index f1aeda39ec..66d0cbaa0c 100644 --- a/src/mcp/server/fastmcp/utilities/context_injection.py +++ b/src/mcp/server/fastmcp/utilities/context_injection.py @@ -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: # If we can't resolve type hints, we can't find the context parameter return None diff --git a/src/mcp/server/fastmcp/utilities/logging.py b/src/mcp/server/fastmcp/utilities/logging.py index 2da0cab32c..43c4d9ba77 100644 --- a/src/mcp/server/fastmcp/utilities/logging.py +++ b/src/mcp/server/fastmcp/utilities/logging.py @@ -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 diff --git a/src/mcp/server/lowlevel/server.py b/src/mcp/server/lowlevel/server.py index 6bea4126ff..919f6cf190 100644 --- a/src/mcp/server/lowlevel/server.py +++ b/src/mcp/server/lowlevel/server.py @@ -233,7 +233,7 @@ def get_capabilities( 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 @@ def create_content(data: str | bytes, mime_type: str | None, meta: dict[str, Any 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 @@ def create_content(data: str | bytes, mime_type: str | None, meta: dict[str, Any ) match result: - case str() | bytes() as data: # pragma: no cover + case str() | bytes() as data: warnings.warn( "Returning str or bytes from read_resource is deprecated. " "Use Iterable[ReadResourceContents] instead.", @@ -416,7 +416,7 @@ def create_content(data: str | bytes, mime_type: str | None, meta: dict[str, Any 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 @@ async def handler(req: types.SetLevelRequest): 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 @@ async def handler(req: types.SubscribeRequest): 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 @@ async def handler(req: types.ListToolsRequest): 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 @@ async def handler(req: types.CallToolRequest): # 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 @@ async def _handle_message( 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 @@ async def _handle_message( case _: await self._handle_notification(message) - for warning in w: # pragma: no cover + for warning in w: logger.info("Warning: %s: %s", warning.category.__name__, warning.message) async def _handle_request( @@ -781,16 +781,16 @@ async def _handle_request( ) ) 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: diff --git a/src/mcp/server/session.py b/src/mcp/server/session.py index 5a70ee02e0..50a441d693 100644 --- a/src/mcp/server/session.py +++ b/src/mcp/server/session.py @@ -114,7 +114,7 @@ def _receive_notification_adapter(self) -> TypeAdapter[types.ClientNotification] @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 @@ def experimental(self) -> ExperimentalServerSessionFeatures: 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 @@ def check_client_capability(self, capability: types.ClientCapabilities) -> bool: 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 @@ def _build_elicit_form_request( # 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 @@ def _build_elicit_url_request( # 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 @@ def _build_create_message_request( # 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 diff --git a/src/mcp/server/streamable_http.py b/src/mcp/server/streamable_http.py index b37a85746d..61abe81b94 100644 --- a/src/mcp/server/streamable_http.py +++ b/src/mcp/server/streamable_http.py @@ -222,7 +222,7 @@ def close_standalone_sse_stream(self) -> None: # pragma: no cover """ 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 @@ def _create_session_message( # pragma: no cover # 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 @@ def _create_error_response( 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 @@ def _create_json_response( # pragma: no cover ) -> 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 @@ def _create_event_data(self, event_message: EventMessage) -> dict[str, str]: # 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 @@ async def handle_request(self, scope: Scope, receive: Receive, send: Send) -> No # 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 @@ def _check_content_type(self, request: Request) -> bool: 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 @@ async def _handle_post_request(self, scope: Scope, request: Request, receive: Re 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 @@ async def _handle_post_request(self, scope: Scope, request: Request, receive: Re ) # 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 @@ async def _handle_post_request(self, scope: Scope, request: Request, receive: Re 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 @@ async def _handle_post_request(self, scope: Scope, request: Request, receive: Re # 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 @@ async def _handle_post_request(self, scope: Scope, request: Request, receive: Re 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 @@ async def terminate(self) -> None: 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 @@ async def connect( # 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 @@ async def message_router(): # pragma: no cover # 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 @@ async def message_router(): # pragma: no cover # 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 @@ async def message_router(): # pragma: no cover 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 @@ -1022,7 +1022,7 @@ async def message_router(): # pragma: no cover else: logger.exception("Unexpected closure of read stream in message router") except Exception: - logger.exception("Error in message router") + logger.exception("Error in message router") # pragma: no cover # Start the message router tg.start_soon(message_router) @@ -1031,7 +1031,7 @@ async def message_router(): # pragma: no cover # 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() diff --git a/src/mcp/server/transport_security.py b/src/mcp/server/transport_security.py index ee1e4505a7..c8c0499013 100644 --- a/src/mcp/server/transport_security.py +++ b/src/mcp/server/transport_security.py @@ -86,9 +86,9 @@ def _validate_origin(self, origin: str | None) -> bool: # pragma: no cover 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 @@ async def validate_request(self, request: Request, is_post: bool = False) -> Res # 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 diff --git a/src/mcp/shared/session.py b/src/mcp/shared/session.py index 341e1fac03..b7d68c15e8 100644 --- a/src/mcp/shared/session.py +++ b/src/mcp/shared/session.py @@ -150,7 +150,7 @@ def in_flight(self) -> bool: # pragma: no cover return not self._completed and not self.cancelled @property - def cancelled(self) -> bool: # pragma: no cover + def cancelled(self) -> bool: return self._cancel_scope.cancel_called @@ -250,11 +250,11 @@ async def send_request( # Set up progress token if progress callback is provided request_data = request.model_dump(by_alias=True, mode="json", exclude_none=True) - if progress_callback is not None: # pragma: no cover + if progress_callback is not None: # Use request_id as progress token - if "params" not in request_data: + if "params" not in request_data: # pragma: lax no cover request_data["params"] = {} - if "_meta" not in request_data["params"]: # pragma: no branch + if "_meta" not in request_data["params"]: # pragma: lax no cover request_data["params"]["_meta"] = {} request_data["params"]["_meta"]["progressToken"] = request_id # Store the callback for this request @@ -304,7 +304,7 @@ async def send_notification( jsonrpc="2.0", **notification.model_dump(by_alias=True, mode="json", exclude_none=True), ) - session_message = SessionMessage( # pragma: no cover + session_message = SessionMessage( message=jsonrpc_notification, metadata=ServerMessageMetadata(related_request_id=related_request_id) if related_request_id else None, ) @@ -384,7 +384,7 @@ async def _receive_loop(self) -> None: await self._in_flight[cancelled_id].cancel() else: # Handle progress notifications callback - if isinstance(notification, ProgressNotification): # pragma: no cover + if isinstance(notification, ProgressNotification): progress_token = notification.params.progress_token # If there is a progress callback for this token, # call it with the progress information @@ -400,9 +400,9 @@ async def _receive_loop(self) -> None: logging.exception("Progress callback raised an exception") await self._received_notification(notification) await self._handle_incoming(notification) - except Exception: # pragma: no cover + except Exception: # For other validation errors, log and continue - logging.warning( + logging.warning( # pragma: no cover f"Failed to validate notification:. Message was: {message.message}", exc_info=True, ) @@ -413,11 +413,11 @@ async def _receive_loop(self) -> None: # This is expected when the client disconnects abruptly. # Without this handler, the exception would propagate up and # crash the server's task group. - logging.debug("Read stream closed by client") # pragma: no cover - except Exception as e: # pragma: no cover + logging.debug("Read stream closed by client") + except Exception as e: # Other exceptions are not expected and should be logged. We purposefully # catch all exceptions here to avoid crashing the server. - logging.exception(f"Unhandled exception in receive loop: {e}") + logging.exception(f"Unhandled exception in receive loop: {e}") # pragma: no cover finally: # after the read stream is closed, we need to send errors # to any pending requests @@ -482,9 +482,9 @@ async def _handle_response(self, message: SessionMessage) -> None: # Fall back to normal response streams stream = self._response_streams.pop(response_id, None) - if stream: # pragma: no cover + if stream: await stream.send(message.message) - else: # pragma: no cover + else: await self._handle_incoming(RuntimeError(f"Received response with an unknown request ID: {message}")) async def _received_request(self, responder: RequestResponder[ReceiveRequestT, SendResultT]) -> None: diff --git a/tests/client/auth/extensions/test_client_credentials.py b/tests/client/auth/extensions/test_client_credentials.py index a4faada4a8..0003b16797 100644 --- a/tests/client/auth/extensions/test_client_credentials.py +++ b/tests/client/auth/extensions/test_client_credentials.py @@ -23,7 +23,7 @@ def __init__(self): self._tokens: OAuthToken | None = None self._client_info: OAuthClientInformationFull | None = None - async def get_tokens(self) -> OAuthToken | None: # pragma: no cover + async def get_tokens(self) -> OAuthToken | None: return self._tokens async def set_tokens(self, tokens: OAuthToken) -> None: # pragma: no cover diff --git a/tests/client/conftest.py b/tests/client/conftest.py index 7314a37351..268e968aa4 100644 --- a/tests/client/conftest.py +++ b/tests/client/conftest.py @@ -40,7 +40,7 @@ def clear(self) -> None: self.client.sent_messages.clear() self.server.sent_messages.clear() - def get_client_requests(self, method: str | None = None) -> list[JSONRPCRequest]: # pragma: no cover + def get_client_requests(self, method: str | None = None) -> list[JSONRPCRequest]: """Get client-sent requests, optionally filtered by method.""" return [ req.message diff --git a/tests/client/test_auth.py b/tests/client/test_auth.py index 2f531cc653..7ad24f2df9 100644 --- a/tests/client/test_auth.py +++ b/tests/client/test_auth.py @@ -1696,7 +1696,7 @@ async def callback_handler() -> tuple[str, str | None]: final_response = httpx.Response(200, request=final_request) try: await auth_flow.asend(final_response) - except StopAsyncIteration: # pragma: no cover + except StopAsyncIteration: pass @pytest.mark.anyio diff --git a/tests/client/test_notification_response.py b/tests/client/test_notification_response.py index 06d893ac68..83d9770974 100644 --- a/tests/client/test_notification_response.py +++ b/tests/client/test_notification_response.py @@ -94,7 +94,7 @@ def non_sdk_server(non_sdk_server_port: int) -> Generator[None, None, None]: proc.start() # Wait for server to be ready - try: # pragma: no cover + try: wait_for_server(non_sdk_server_port, timeout=10.0) except TimeoutError: # pragma: no cover proc.kill() diff --git a/tests/client/test_stdio.py b/tests/client/test_stdio.py index 4059a92682..088669f340 100644 --- a/tests/client/test_stdio.py +++ b/tests/client/test_stdio.py @@ -157,7 +157,7 @@ async def test_stdio_client_universal_cleanup(): @pytest.mark.anyio @pytest.mark.skipif(sys.platform == "win32", reason="Windows signal handling is different") -async def test_stdio_client_sigint_only_process(): # pragma: no cover +async def test_stdio_client_sigint_only_process(): """Test cleanup with a process that ignores SIGTERM but responds to SIGINT.""" # Create a Python script that ignores SIGTERM but handles SIGINT script_content = textwrap.dedent( @@ -481,7 +481,7 @@ def handle_term(sig, frame): await anyio.sleep(0.5) # Verify child is writing - if os.path.exists(marker_file): # pragma: no cover + if os.path.exists(marker_file): # pragma: no branch size1 = os.path.getsize(marker_file) await anyio.sleep(0.3) size2 = os.path.getsize(marker_file) diff --git a/tests/client/transports/test_memory.py b/tests/client/transports/test_memory.py index b97ebcea2f..942ff6c8e0 100644 --- a/tests/client/transports/test_memory.py +++ b/tests/client/transports/test_memory.py @@ -34,10 +34,8 @@ def fastmcp_server() -> FastMCP: """Create a FastMCP server for testing.""" server = FastMCP("test") - # pragma: no cover on handlers below - they exist only to register capabilities. - # Transport tests verify stream creation and basic protocol, not handler invocation. @server.tool() - def greet(name: str) -> str: # pragma: no cover + def greet(name: str) -> str: """Greet someone by name.""" return f"Hello, {name}!" diff --git a/tests/experimental/tasks/server/test_server.py b/tests/experimental/tasks/server/test_server.py index cb8b737f28..f8483e22d3 100644 --- a/tests/experimental/tasks/server/test_server.py +++ b/tests/experimental/tasks/server/test_server.py @@ -310,8 +310,7 @@ async def run_server(): async with anyio.create_task_group() as tg: async def handle_messages(): - # TODO(Marcelo): Drop the pragma once https://github.com/coveragepy/coveragepy/issues/1987 is fixed. - async for message in server_session.incoming_messages: # pragma: no cover + async for message in server_session.incoming_messages: await server._handle_message(message, server_session, {}, False) tg.start_soon(handle_messages) @@ -388,8 +387,8 @@ async def run_server(): ), ) as server_session: async with anyio.create_task_group() as tg: - # TODO(Marcelo): Drop the pragma once https://github.com/coveragepy/coveragepy/issues/1987 is fixed. - async def handle_messages(): # pragma: no cover + + async def handle_messages(): async for message in server_session.incoming_messages: await server._handle_message(message, server_session, {}, False) @@ -573,7 +572,7 @@ async def test_build_elicit_form_request() -> None: assert ( request_with_task.params["_meta"]["io.modelcontextprotocol/related-task"]["taskId"] == "test-task-123" ) - finally: # pragma: no cover + finally: await server_to_client_send.aclose() await server_to_client_receive.aclose() await client_to_server_send.aclose() @@ -619,7 +618,7 @@ async def test_build_elicit_url_request() -> None: assert ( request_with_task.params["_meta"]["io.modelcontextprotocol/related-task"]["taskId"] == "test-task-789" ) - finally: # pragma: no cover + finally: await server_to_client_send.aclose() await server_to_client_receive.aclose() await client_to_server_send.aclose() @@ -670,7 +669,7 @@ async def test_build_create_message_request() -> None: request_with_task.params["_meta"]["io.modelcontextprotocol/related-task"]["taskId"] == "sampling-task-456" ) - finally: # pragma: no cover + finally: await server_to_client_send.aclose() await server_to_client_receive.aclose() await client_to_server_send.aclose() @@ -707,7 +706,7 @@ async def test_send_message() -> None: received = await server_to_client_receive.receive() assert isinstance(received.message, JSONRPCNotification) assert received.message.method == "test/notification" - finally: # pragma: no cover + finally: await server_to_client_send.aclose() await server_to_client_receive.aclose() await client_to_server_send.aclose() @@ -761,7 +760,7 @@ def route_error(self, request_id: str | int, error: ErrorData) -> bool: assert len(routed_responses) == 1 assert routed_responses[0]["id"] == "test-req-1" assert routed_responses[0]["response"]["status"] == "ok" - finally: # pragma: no cover + finally: await server_to_client_send.aclose() await server_to_client_receive.aclose() await client_to_server_send.aclose() @@ -816,7 +815,7 @@ def route_error(self, request_id: str | int, error: ErrorData) -> bool: assert len(routed_errors) == 1 assert routed_errors[0]["id"] == "test-req-2" assert routed_errors[0]["error"].message == "Test error" - finally: # pragma: no cover + finally: await server_to_client_send.aclose() await server_to_client_receive.aclose() await client_to_server_send.aclose() @@ -874,7 +873,7 @@ def route_error(self, request_id: str | int, error: ErrorData) -> bool: # Verify both routers were called (first returned False, second returned True) assert router_calls == ["non_matching_response", "matching_response"] - finally: # pragma: no cover + finally: await server_to_client_send.aclose() await server_to_client_receive.aclose() await client_to_server_send.aclose() @@ -933,7 +932,7 @@ def route_error(self, request_id: str | int, error: ErrorData) -> bool: # Verify both routers were called (first returned False, second returned True) assert router_calls == ["non_matching_error", "matching_error"] - finally: # pragma: no cover + finally: await server_to_client_send.aclose() await server_to_client_receive.aclose() await client_to_server_send.aclose() diff --git a/tests/issues/test_1027_win_unreachable_cleanup.py b/tests/issues/test_1027_win_unreachable_cleanup.py index 0c569edb2a..78ff54070f 100644 --- a/tests/issues/test_1027_win_unreachable_cleanup.py +++ b/tests/issues/test_1027_win_unreachable_cleanup.py @@ -102,7 +102,7 @@ def echo(text: str) -> str: # Give server a moment to complete cleanup with anyio.move_on_after(5.0): - while not Path(cleanup_marker).exists(): # pragma: no cover + while not Path(cleanup_marker).exists(): # pragma: lax no cover await anyio.sleep(0.1) # Verify cleanup marker was created - this works now that stdio_client @@ -113,9 +113,9 @@ def echo(text: str) -> str: finally: # Clean up files for path in [server_script, startup_marker, cleanup_marker]: - try: # pragma: no cover + try: # pragma: lax no cover Path(path).unlink() - except FileNotFoundError: # pragma: no cover + except FileNotFoundError: # pragma: lax no cover pass @@ -204,7 +204,7 @@ def echo(text: str) -> str: await anyio.sleep(0.1) # Check if process is still running - if hasattr(process, "returncode") and process.returncode is not None: # pragma: no cover + if hasattr(process, "returncode") and process.returncode is not None: # pragma: lax no cover pytest.fail(f"Server process exited with code {process.returncode}") assert Path(startup_marker).exists(), "Server startup marker not created" @@ -217,14 +217,14 @@ def echo(text: str) -> str: try: with anyio.fail_after(5.0): # Increased from 2.0 to 5.0 await process.wait() - except TimeoutError: # pragma: no cover + except TimeoutError: # pragma: lax no cover # If it doesn't exit after stdin close, terminate it process.terminate() await process.wait() # Check if cleanup ran with anyio.move_on_after(5.0): - while not Path(cleanup_marker).exists(): # pragma: no cover + while not Path(cleanup_marker).exists(): # pragma: lax no cover await anyio.sleep(0.1) # Verify the cleanup ran - stdin closure enables graceful shutdown @@ -234,7 +234,7 @@ def echo(text: str) -> str: finally: # Clean up files for path in [server_script, startup_marker, cleanup_marker]: - try: # pragma: no cover + try: # pragma: lax no cover Path(path).unlink() - except FileNotFoundError: # pragma: no cover + except FileNotFoundError: # pragma: lax no cover pass diff --git a/tests/issues/test_129_resource_templates.py b/tests/issues/test_129_resource_templates.py index 26b58343c3..2411210675 100644 --- a/tests/issues/test_129_resource_templates.py +++ b/tests/issues/test_129_resource_templates.py @@ -33,10 +33,10 @@ def get_user_profile(user_id: str) -> str: # pragma: no cover assert len(templates) == 2 # Verify template details - greeting_template = next(t for t in templates if t.name == "get_greeting") # pragma: no cover + greeting_template = next(t for t in templates if t.name == "get_greeting") assert greeting_template.uri_template == "greeting://{name}" assert greeting_template.description == "Get a personalized greeting" - profile_template = next(t for t in templates if t.name == "get_user_profile") # pragma: no cover + profile_template = next(t for t in templates if t.name == "get_user_profile") assert profile_template.uri_template == "users://{user_id}/profile" assert profile_template.description == "Dynamic user data" diff --git a/tests/issues/test_1363_race_condition_streamable_http.py b/tests/issues/test_1363_race_condition_streamable_http.py index caa6db46ec..db2a82d073 100644 --- a/tests/issues/test_1363_race_condition_streamable_http.py +++ b/tests/issues/test_1363_race_condition_streamable_http.py @@ -99,7 +99,7 @@ def check_logs_for_race_condition_errors(caplog: pytest.LogCaptureFixture, test_ # Check for specific race condition errors in logs errors_found: list[str] = [] - for record in caplog.records: # pragma: no cover + for record in caplog.records: # pragma: lax no cover message = record.getMessage() if "ClosedResourceError" in message: errors_found.append("ClosedResourceError") diff --git a/tests/issues/test_88_random_error.py b/tests/issues/test_88_random_error.py index a29231d77d..c37d69140f 100644 --- a/tests/issues/test_88_random_error.py +++ b/tests/issues/test_88_random_error.py @@ -104,7 +104,7 @@ async def client( # proving server is still responsive result = await session.call_tool("fast", read_timeout_seconds=None) assert result.content == [TextContent(type="text", text="fast 3")] - scope.cancel() # pragma: no cover + scope.cancel() # Run server and client in separate task groups to avoid cancellation server_writer, server_reader = anyio.create_memory_object_stream[SessionMessage](1) diff --git a/tests/issues/test_malformed_input.py b/tests/issues/test_malformed_input.py index cb60ca42a6..3db95c9ce5 100644 --- a/tests/issues/test_malformed_input.py +++ b/tests/issues/test_malformed_input.py @@ -82,7 +82,7 @@ async def test_malformed_initialize_request_does_not_crash_server(): except anyio.WouldBlock: # pragma: no cover pytest.fail("No response received - server likely crashed") - finally: # pragma: no cover + finally: # Close all streams to ensure proper cleanup await read_send_stream.aclose() await write_send_stream.aclose() @@ -143,7 +143,7 @@ async def test_multiple_concurrent_malformed_requests(): assert isinstance(response, JSONRPCError) assert response.id == f"malformed_{i}" assert response.error.code == INVALID_PARAMS - finally: # pragma: no cover + finally: # Close all streams to ensure proper cleanup await read_send_stream.aclose() await write_send_stream.aclose() diff --git a/tests/server/fastmcp/auth/test_auth_integration.py b/tests/server/fastmcp/auth/test_auth_integration.py index 5000c7b386..a78a86cf0b 100644 --- a/tests/server/fastmcp/auth/test_auth_integration.py +++ b/tests/server/fastmcp/auth/test_auth_integration.py @@ -172,7 +172,7 @@ async def load_access_token(self, token: str) -> AccessToken | None: async def revoke_token(self, token: AccessToken | RefreshToken) -> None: match token: - case RefreshToken(): # pragma: no cover + case RefreshToken(): # pragma: lax no cover # Remove the refresh token del self.refresh_tokens[token.token] diff --git a/tests/server/fastmcp/resources/test_file_resources.py b/tests/server/fastmcp/resources/test_file_resources.py index 0eb24f0632..6178f8f438 100644 --- a/tests/server/fastmcp/resources/test_file_resources.py +++ b/tests/server/fastmcp/resources/test_file_resources.py @@ -18,9 +18,9 @@ def temp_file(): f.write(content) path = Path(f.name).resolve() yield path - try: # pragma: no cover + try: # pragma: lax no cover path.unlink() - except FileNotFoundError: # pragma: no cover + except FileNotFoundError: # pragma: lax no cover pass # File was already deleted by the test @@ -101,7 +101,7 @@ async def test_missing_file_error(self, temp_file: Path): @pytest.mark.skipif(os.name == "nt", reason="File permissions behave differently on Windows") @pytest.mark.anyio - async def test_permission_error(self, temp_file: Path): # pragma: no cover + async def test_permission_error(self, temp_file: Path): """Test reading a file without permissions.""" temp_file.chmod(0o000) # Remove all permissions try: diff --git a/tests/server/fastmcp/resources/test_resource_manager.py b/tests/server/fastmcp/resources/test_resource_manager.py index 5fd4bc8529..3d6dd9be91 100644 --- a/tests/server/fastmcp/resources/test_resource_manager.py +++ b/tests/server/fastmcp/resources/test_resource_manager.py @@ -18,9 +18,9 @@ def temp_file(): f.write(content) path = Path(f.name).resolve() yield path - try: # pragma: no cover + try: # pragma: lax no cover path.unlink() - except FileNotFoundError: # pragma: no cover + except FileNotFoundError: # pragma: lax no cover pass # File was already deleted by the test diff --git a/tests/server/fastmcp/resources/test_resource_template.py b/tests/server/fastmcp/resources/test_resource_template.py index f3d3ba5e45..022e86db82 100644 --- a/tests/server/fastmcp/resources/test_resource_template.py +++ b/tests/server/fastmcp/resources/test_resource_template.py @@ -15,7 +15,7 @@ class TestResourceTemplate: def test_template_creation(self): """Test creating a template from a function.""" - def my_func(key: str, value: int) -> dict[str, Any]: # pragma: no cover + def my_func(key: str, value: int) -> dict[str, Any]: return {"key": key, "value": value} template = ResourceTemplate.from_function( @@ -239,7 +239,7 @@ def get_dynamic(id: str) -> str: # pragma: no cover async def test_template_created_resources_inherit_annotations(self): """Test that resources created from templates inherit annotations.""" - def get_item(item_id: str) -> str: # pragma: no cover + def get_item(item_id: str) -> str: return f"Item {item_id}" annotations = Annotations(priority=0.6) diff --git a/tests/server/fastmcp/test_elicitation.py b/tests/server/fastmcp/test_elicitation.py index efed572e4b..587dcebb06 100644 --- a/tests/server/fastmcp/test_elicitation.py +++ b/tests/server/fastmcp/test_elicitation.py @@ -65,12 +65,10 @@ async def test_stdio_elicitation(): create_ask_user_tool(mcp) # Create a custom handler for elicitation requests - async def elicitation_callback( - context: RequestContext[ClientSession, None], params: ElicitRequestParams - ): # pragma: no cover + async def elicitation_callback(context: RequestContext[ClientSession, None], params: ElicitRequestParams): if params.message == "Tool wants to ask: What is your name?": return ElicitResult(action="accept", content={"answer": "Test User"}) - else: + else: # pragma: no cover raise ValueError(f"Unexpected elicitation message: {params.message}") await call_tool_and_assert( @@ -99,10 +97,10 @@ async def test_elicitation_schema_validation(): def create_validation_tool(name: str, schema_class: type[BaseModel]): @mcp.tool(name=name, description=f"Tool testing {name}") - async def tool(ctx: Context[ServerSession, None]) -> str: # pragma: no cover + async def tool(ctx: Context[ServerSession, None]) -> str: try: await ctx.elicit(message="This should fail validation", schema=schema_class) - return "Should not reach here" + return "Should not reach here" # pragma: no cover except TypeError as e: return f"Validation failed as expected: {str(e)}" @@ -190,10 +188,10 @@ class InvalidOptionalSchema(BaseModel): optional_list: list[int] | None = Field(default=None, description="Invalid optional list") @mcp.tool(description="Tool with invalid optional field") - async def invalid_optional_tool(ctx: Context[ServerSession, None]) -> str: # pragma: no cover + async def invalid_optional_tool(ctx: Context[ServerSession, None]) -> str: try: await ctx.elicit(message="This should fail", schema=InvalidOptionalSchema) - return "Should not reach here" + return "Should not reach here" # pragma: no cover except TypeError as e: return f"Validation failed: {str(e)}" diff --git a/tests/server/fastmcp/test_func_metadata.py b/tests/server/fastmcp/test_func_metadata.py index 8d3ac6ec50..ba1d30d26d 100644 --- a/tests/server/fastmcp/test_func_metadata.py +++ b/tests/server/fastmcp/test_func_metadata.py @@ -516,7 +516,7 @@ async def test_str_annotation_runtime_validation(): containing valid JSON to ensure they are passed as strings, not parsed objects. """ - def handle_json_payload(payload: str, strict_mode: bool = False) -> str: # pragma: no cover + def handle_json_payload(payload: str, strict_mode: bool = False) -> str: """Function that processes a JSON payload as a string.""" # This function expects to receive the raw JSON string # It might parse it later after validation or logging @@ -833,7 +833,7 @@ def func_returning_unannotated() -> UnannotatedClass: # pragma: no cover def test_tool_call_result_is_unstructured_and_not_converted(): - def func_returning_call_tool_result() -> CallToolResult: # pragma: no cover + def func_returning_call_tool_result() -> CallToolResult: return CallToolResult(content=[]) meta = func_metadata(func_returning_call_tool_result) @@ -846,7 +846,7 @@ def test_tool_call_result_annotated_is_structured_and_converted(): class PersonClass(BaseModel): name: str - def func_returning_annotated_tool_call_result() -> Annotated[CallToolResult, PersonClass]: # pragma: no cover + def func_returning_annotated_tool_call_result() -> Annotated[CallToolResult, PersonClass]: return CallToolResult(content=[], structured_content={"name": "Brandon"}) meta = func_metadata(func_returning_annotated_tool_call_result) @@ -866,7 +866,7 @@ def test_tool_call_result_annotated_is_structured_and_invalid(): class PersonClass(BaseModel): name: str - def func_returning_annotated_tool_call_result() -> Annotated[CallToolResult, PersonClass]: # pragma: no cover + def func_returning_annotated_tool_call_result() -> Annotated[CallToolResult, PersonClass]: return CallToolResult(content=[], structured_content={"person": "Brandon"}) meta = func_metadata(func_returning_annotated_tool_call_result) @@ -1092,7 +1092,7 @@ def func_with_reserved_names( # pragma: no cover async def test_basemodel_reserved_names_validation(): """Test that validation and calling works with reserved parameter names""" - def func_with_reserved_names( # pragma: no cover + def func_with_reserved_names( model_dump: str, model_validate: int, dict: list[str], diff --git a/tests/server/fastmcp/test_server.py b/tests/server/fastmcp/test_server.py index 6d1cee58ef..5b6030be96 100644 --- a/tests/server/fastmcp/test_server.py +++ b/tests/server/fastmcp/test_server.py @@ -887,7 +887,7 @@ async def test_resource_multiple_mismatched_params(self): def get_data_mismatched(org: str, repo_2: str) -> str: # pragma: no cover return f"Data for {org}" - """Test that a resource with no parameters works as a regular resource""" # pragma: no cover + """Test that a resource with no parameters works as a regular resource""" mcp = FastMCP() @mcp.resource("resource://static") diff --git a/tests/server/fastmcp/test_tool_manager.py b/tests/server/fastmcp/test_tool_manager.py index b09ae7de15..5ac1bf282f 100644 --- a/tests/server/fastmcp/test_tool_manager.py +++ b/tests/server/fastmcp/test_tool_manager.py @@ -182,7 +182,7 @@ def f(x: int) -> int: # pragma: no cover class TestCallTools: @pytest.mark.anyio async def test_call_tool(self): - def sum(a: int, b: int) -> int: # pragma: no cover + def sum(a: int, b: int) -> int: """Add two numbers.""" return a + b @@ -193,7 +193,7 @@ def sum(a: int, b: int) -> int: # pragma: no cover @pytest.mark.anyio async def test_call_async_tool(self): - async def double(n: int) -> int: # pragma: no cover + async def double(n: int) -> int: """Double a number.""" return n * 2 @@ -260,7 +260,7 @@ async def test_call_unknown_tool(self): @pytest.mark.anyio async def test_call_tool_with_list_int_input(self): - def sum_vals(vals: list[int]) -> int: # pragma: no cover + def sum_vals(vals: list[int]) -> int: return sum(vals) manager = ToolManager() @@ -273,7 +273,7 @@ def sum_vals(vals: list[int]) -> int: # pragma: no cover @pytest.mark.anyio async def test_call_tool_with_list_str_or_str_input(self): - def concat_strs(vals: list[str] | str) -> str: # pragma: no cover + def concat_strs(vals: list[str] | str) -> str: return vals if isinstance(vals, str) else "".join(vals) manager = ToolManager() @@ -297,7 +297,7 @@ class Shrimp(BaseModel): shrimp: list[Shrimp] x: None - def name_shrimp(tank: MyShrimpTank, ctx: Context[ServerSessionT, None]) -> list[str]: # pragma: no cover + def name_shrimp(tank: MyShrimpTank, ctx: Context[ServerSessionT, None]) -> list[str]: return [x.name for x in tank.shrimp] manager = ToolManager() @@ -375,7 +375,7 @@ def tool_with_context(x: int, ctx: Context[ServerSessionT, None]) -> str: async def test_context_injection_async(self): """Test that context is properly injected in async tools.""" - async def async_tool(x: int, ctx: Context[ServerSessionT, None]) -> str: # pragma: no cover + async def async_tool(x: int, ctx: Context[ServerSessionT, None]) -> str: assert isinstance(ctx, Context) return str(x) @@ -467,7 +467,7 @@ class UserOutput(BaseModel): name: str age: int - def get_user(user_id: int) -> UserOutput: # pragma: no cover + def get_user(user_id: int) -> UserOutput: """Get user by ID.""" return UserOutput(name="John", age=30) @@ -481,7 +481,7 @@ def get_user(user_id: int) -> UserOutput: # pragma: no cover async def test_tool_with_primitive_output(self): """Test tool with primitive return type.""" - def double_number(n: int) -> int: # pragma: no cover + def double_number(n: int) -> int: """Double a number.""" return 10 @@ -502,7 +502,7 @@ class UserDict(TypedDict): expected_output = {"name": "Alice", "age": 25} - def get_user_dict(user_id: int) -> UserDict: # pragma: no cover + def get_user_dict(user_id: int) -> UserDict: """Get user as dict.""" return UserDict(name="Alice", age=25) @@ -522,7 +522,7 @@ class Person: expected_output = {"name": "Bob", "age": 40} - def get_person() -> Person: # pragma: no cover + def get_person() -> Person: """Get a person.""" return Person("Bob", 40) @@ -539,7 +539,7 @@ async def test_tool_with_list_output(self): expected_list = [1, 2, 3, 4, 5] expected_output = {"result": expected_list} - def get_numbers() -> list[int]: # pragma: no cover + def get_numbers() -> list[int]: """Get a list of numbers.""" return expected_list @@ -590,7 +590,7 @@ def get_user() -> UserOutput: # pragma: no cover async def test_tool_with_dict_str_any_output(self): """Test tool with dict[str, Any] return type.""" - def get_config() -> dict[str, Any]: # pragma: no cover + def get_config() -> dict[str, Any]: """Get configuration""" return {"debug": True, "port": 8080, "features": ["auth", "logging"]} @@ -615,7 +615,7 @@ def get_config() -> dict[str, Any]: # pragma: no cover async def test_tool_with_dict_str_typed_output(self): """Test tool with dict[str, T] return type for specific T.""" - def get_scores() -> dict[str, int]: # pragma: no cover + def get_scores() -> dict[str, int]: """Get player scores""" return {"alice": 100, "bob": 85, "charlie": 92} @@ -879,7 +879,7 @@ def divide(a: int, b: int) -> float: # pragma: no cover async def test_call_removed_tool_raises_error(self): """Test that calling a removed tool raises ToolError.""" - def greet(name: str) -> str: # pragma: no cover + def greet(name: str) -> str: """Greet someone.""" return f"Hello, {name}!" diff --git a/tests/server/test_completion_with_context.py b/tests/server/test_completion_with_context.py index 19c591340d..5a8d67f09e 100644 --- a/tests/server/test_completion_with_context.py +++ b/tests/server/test_completion_with_context.py @@ -102,7 +102,7 @@ async def handle_completion( db = context.arguments.get("database") if db == "users_db": return Completion(values=["users", "sessions", "permissions"], total=3, has_more=False) - elif db == "products_db": # pragma: no cover + elif db == "products_db": return Completion(values=["products", "categories", "inventory"], total=3, has_more=False) return Completion(values=[], total=0, has_more=False) # pragma: no cover @@ -153,7 +153,7 @@ async def handle_completion( raise ValueError("Please select a database first to see available tables") # Normal completion if context is provided db = context.arguments.get("database") - if db == "test_db": # pragma: no cover + if db == "test_db": return Completion(values=["users", "orders", "products"], total=3, has_more=False) return Completion(values=[], total=0, has_more=False) # pragma: no cover diff --git a/tests/server/test_lowlevel_input_validation.py b/tests/server/test_lowlevel_input_validation.py index eb644938ff..a108b23380 100644 --- a/tests/server/test_lowlevel_input_validation.py +++ b/tests/server/test_lowlevel_input_validation.py @@ -70,8 +70,7 @@ async def run_server(): async with anyio.create_task_group() as tg: async def handle_messages(): - # TODO(Marcelo): Drop the pragma once https://github.com/coveragepy/coveragepy/issues/1987 is fixed. - async for message in server_session.incoming_messages: # pragma: no cover + async for message in server_session.incoming_messages: await server._handle_message(message, server_session, {}, False) tg.start_soon(handle_messages) diff --git a/tests/server/test_lowlevel_output_validation.py b/tests/server/test_lowlevel_output_validation.py index 3b1b7236b5..ed88775f8b 100644 --- a/tests/server/test_lowlevel_output_validation.py +++ b/tests/server/test_lowlevel_output_validation.py @@ -71,8 +71,7 @@ async def run_server(): async with anyio.create_task_group() as tg: async def handle_messages(): - # TODO(Marcelo): Drop the pragma once https://github.com/coveragepy/coveragepy/issues/1987 is fixed. - async for message in server_session.incoming_messages: # pragma: no cover + async for message in server_session.incoming_messages: await server._handle_message(message, server_session, {}, False) tg.start_soon(handle_messages) diff --git a/tests/server/test_lowlevel_tool_annotations.py b/tests/server/test_lowlevel_tool_annotations.py index 614ca2dce5..1c60201f59 100644 --- a/tests/server/test_lowlevel_tool_annotations.py +++ b/tests/server/test_lowlevel_tool_annotations.py @@ -20,7 +20,7 @@ async def test_lowlevel_server_tool_annotations(): # Create a tool with annotations @server.list_tools() - async def list_tools(): # pragma: no cover + async def list_tools(): return [ Tool( name="echo", @@ -67,8 +67,7 @@ async def run_server(): async with anyio.create_task_group() as tg: async def handle_messages(): - # TODO(Marcelo): Drop the pragma once https://github.com/coveragepy/coveragepy/issues/1987 is fixed. - async for message in server_session.incoming_messages: # pragma: no cover + async for message in server_session.incoming_messages: await server._handle_message(message, server_session, {}, False) tg.start_soon(handle_messages) diff --git a/tests/server/test_session.py b/tests/server/test_session.py index 5de9882223..ed39033705 100644 --- a/tests/server/test_session.py +++ b/tests/server/test_session.py @@ -391,8 +391,7 @@ async def test_create_message_tool_result_validation(): # Case 8: empty messages list - skips validation entirely # Covers the `if messages:` branch (line 280->302) - # TODO(Marcelo): Drop the pragma once https://github.com/coveragepy/coveragepy/issues/1987 is fixed. - with anyio.move_on_after(0.01): # pragma: no cover + with anyio.move_on_after(0.01): await session.create_message(messages=[], max_tokens=100) diff --git a/tests/shared/test_progress_notifications.py b/tests/shared/test_progress_notifications.py index 13edcec01d..81aa1ccbc5 100644 --- a/tests/shared/test_progress_notifications.py +++ b/tests/shared/test_progress_notifications.py @@ -335,9 +335,7 @@ def mock_log_exception(msg: str, *args: Any, **kwargs: Any) -> None: logged_errors.append(msg % args if args else msg) # Create a progress callback that raises an exception - async def failing_progress_callback( - progress: float, total: float | None, message: str | None - ) -> None: # pragma: no cover + async def failing_progress_callback(progress: float, total: float | None, message: str | None) -> None: raise ValueError("Progress callback failed!") # Create a server with a tool that sends progress notifications diff --git a/tests/shared/test_session.py b/tests/shared/test_session.py index 89fe18ebbc..2a44660eac 100644 --- a/tests/shared/test_session.py +++ b/tests/shared/test_session.py @@ -97,8 +97,7 @@ async def make_request(client: Client): ) # Give cancellation time to process - # TODO(Marcelo): Drop the pragma once https://github.com/coveragepy/coveragepy/issues/1987 is fixed. - with anyio.fail_after(1): # pragma: no cover + with anyio.fail_after(1): await ev_cancelled.wait() @@ -149,8 +148,7 @@ async def make_request(client_session: ClientSession): tg.start_soon(mock_server) tg.start_soon(make_request, client_session) - # TODO(Marcelo): Drop the pragma once https://github.com/coveragepy/coveragepy/issues/1987 is fixed. - with anyio.fail_after(2): # pragma: no cover + with anyio.fail_after(2): await ev_response_received.wait() assert len(result_holder) == 1 @@ -205,8 +203,7 @@ async def make_request(client_session: ClientSession): tg.start_soon(mock_server) tg.start_soon(make_request, client_session) - # TODO(Marcelo): Drop the pragma once https://github.com/coveragepy/coveragepy/issues/1987 is fixed. - with anyio.fail_after(2): # pragma: no cover + with anyio.fail_after(2): await ev_error_received.wait() assert len(error_holder) == 1 @@ -260,8 +257,7 @@ async def make_request(client_session: ClientSession): tg.start_soon(mock_server) tg.start_soon(make_request, client_session) - # TODO(Marcelo): Drop the pragma once https://github.com/coveragepy/coveragepy/issues/1987 is fixed. - with anyio.fail_after(2): # pragma: no cover + with anyio.fail_after(2): await ev_timeout.wait() @@ -305,8 +301,7 @@ async def mock_server(): tg.start_soon(make_request, client_session) tg.start_soon(mock_server) - # TODO(Marcelo): Drop the pragma once https://github.com/coveragepy/coveragepy/issues/1987 is fixed. - with anyio.fail_after(1): # pragma: no cover + with anyio.fail_after(1): await ev_closed.wait() - with anyio.fail_after(1): # pragma: no cover + with anyio.fail_after(1): await ev_response.wait() diff --git a/tests/shared/test_streamable_http.py b/tests/shared/test_streamable_http.py index ed86f9860e..a3086ab6bb 100644 --- a/tests/shared/test_streamable_http.py +++ b/tests/shared/test_streamable_http.py @@ -72,14 +72,14 @@ # Helper functions -def extract_protocol_version_from_sse(response: requests.Response) -> str: # pragma: no cover +def extract_protocol_version_from_sse(response: requests.Response) -> str: """Extract the negotiated protocol version from an SSE initialization response.""" assert response.headers.get("Content-Type") == "text/event-stream" for line in response.text.splitlines(): if line.startswith("data: "): init_data = json.loads(line[6:]) return init_data["result"]["protocolVersion"] - raise ValueError("Could not extract protocol version from SSE response") + raise ValueError("Could not extract protocol version from SSE response") # pragma: no cover # Simple in-memory event store for testing @@ -90,9 +90,7 @@ def __init__(self): self._events: list[tuple[StreamId, EventId, types.JSONRPCMessage | None]] = [] self._event_id_counter = 0 - async def store_event( # pragma: no cover - self, stream_id: StreamId, message: types.JSONRPCMessage | None - ) -> EventId: + async def store_event(self, stream_id: StreamId, message: types.JSONRPCMessage | None) -> EventId: """Store an event and return its ID.""" self._event_id_counter += 1 event_id = str(self._event_id_counter) @@ -871,7 +869,7 @@ def test_get_sse_stream(basic_server: None, basic_server_url: str): init_data = None assert init_response.headers.get("Content-Type") == "text/event-stream" for line in init_response.text.splitlines(): # pragma: no branch - if line.startswith("data: "): # pragma: no cover + if line.startswith("data: "): init_data = json.loads(line[6:]) break assert init_data is not None @@ -931,7 +929,7 @@ def test_get_validation(basic_server: None, basic_server_url: str): init_data = None assert init_response.headers.get("Content-Type") == "text/event-stream" for line in init_response.text.splitlines(): # pragma: no branch - if line.startswith("data: "): # pragma: no cover + if line.startswith("data: "): init_data = json.loads(line[6:]) break assert init_data is not None @@ -1155,8 +1153,8 @@ async def test_streamable_http_client_session_termination(basic_server: None, ba tools = await session.list_tools() assert len(tools.tools) == 10 - headers: dict[str, str] = {} # pragma: no cover - if captured_session_id: # pragma: no cover + headers: dict[str, str] = {} + if captured_session_id: # pragma: no branch headers[MCP_SESSION_ID_HEADER] = captured_session_id async with create_mcp_http_client(headers=headers) as httpx_client: @@ -1219,8 +1217,8 @@ async def mock_delete(self: httpx.AsyncClient, *args: Any, **kwargs: Any) -> htt tools = await session.list_tools() assert len(tools.tools) == 10 - headers: dict[str, str] = {} # pragma: no cover - if captured_session_id: # pragma: no cover + headers: dict[str, str] = {} + if captured_session_id: # pragma: no branch headers[MCP_SESSION_ID_HEADER] = captured_session_id async with create_mcp_http_client(headers=headers) as httpx_client: @@ -1305,18 +1303,18 @@ async def run_tool(): tg.cancel_scope.cancel() # Verify we received exactly one notification - assert len(captured_notifications) == 1 # pragma: no cover - assert isinstance(captured_notifications[0], types.LoggingMessageNotification) # pragma: no cover - assert captured_notifications[0].params.data == "First notification before lock" # pragma: no cover + assert len(captured_notifications) == 1 + assert isinstance(captured_notifications[0], types.LoggingMessageNotification) + assert captured_notifications[0].params.data == "First notification before lock" # Clear notifications for the second phase - captured_notifications = [] # pragma: no cover + captured_notifications = [] # Now resume the session with the same mcp-session-id and protocol version - headers: dict[str, Any] = {} # pragma: no cover - if captured_session_id: # pragma: no cover + headers: dict[str, Any] = {} + if captured_session_id: # pragma: no branch headers[MCP_SESSION_ID_HEADER] = captured_session_id - if captured_protocol_version: # pragma: no cover + if captured_protocol_version: # pragma: no branch headers[MCP_PROTOCOL_VERSION_HEADER] = captured_protocol_version async with create_mcp_http_client(headers=headers) as httpx_client: @@ -1348,8 +1346,8 @@ async def run_tool(): # We should have received the remaining notifications assert len(captured_notifications) == 1 - assert isinstance(captured_notifications[0], types.LoggingMessageNotification) # pragma: no cover - assert captured_notifications[0].params.data == "Second notification after lock" # pragma: no cover + assert isinstance(captured_notifications[0], types.LoggingMessageNotification) + assert captured_notifications[0].params.data == "Second notification after lock" @pytest.mark.anyio @@ -1582,8 +1580,8 @@ async def test_streamablehttp_request_context_isolation(context_aware_server: No contexts.append(context_data) # Verify each request had its own context - assert len(contexts) == 3 # pragma: no cover - for i, ctx in enumerate(contexts): # pragma: no cover + assert len(contexts) == 3 + for i, ctx in enumerate(contexts): assert ctx["request_id"] == f"request-{i}" assert ctx["headers"].get("x-request-id") == f"request-{i}" assert ctx["headers"].get("x-custom-value") == f"value-{i}" @@ -2153,7 +2151,7 @@ async def on_resumption_token(token: str) -> None: assert "Completed 3 checkpoints" in result.content[0].text # 4 priming + 3 notifications + 1 response = 8 tokens - assert len(resumption_tokens) == 8, ( # pragma: no cover + assert len(resumption_tokens) == 8, ( f"Expected 8 resumption tokens (4 priming + 3 notifs + 1 response), " f"got {len(resumption_tokens)}: {resumption_tokens}" ) diff --git a/tests/shared/test_ws.py b/tests/shared/test_ws.py index 8fb7aeec3b..501fe049b7 100644 --- a/tests/shared/test_ws.py +++ b/tests/shared/test_ws.py @@ -105,7 +105,7 @@ def run_server(server_port: int) -> None: # pragma: no cover time.sleep(0.5) -@pytest.fixture() # pragma: no cover +@pytest.fixture() def server(server_port: int) -> Generator[None, None, None]: proc = multiprocessing.Process(target=run_server, kwargs={"server_port": server_port}, daemon=True) print("starting process") diff --git a/tests/test_examples.py b/tests/test_examples.py index 327729d4a4..adc6942e29 100644 --- a/tests/test_examples.py +++ b/tests/test_examples.py @@ -92,7 +92,7 @@ async def test_desktop(monkeypatch: pytest.MonkeyPatch): assert file_1 in content.text assert file_2 in content.text # might be a bug, but the test is passing - else: # pragma: no cover + else: assert "/fake/path/file1.txt" in content.text assert "/fake/path/file2.txt" in content.text diff --git a/uv.lock b/uv.lock index 3bd36a8753..821edee8a0 100644 --- a/uv.lock +++ b/uv.lock @@ -306,101 +306,101 @@ wheels = [ [[package]] name = "coverage" -version = "7.13.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/23/f9/e92df5e07f3fc8d4c7f9a0f146ef75446bf870351cd37b788cf5897f8079/coverage-7.13.1.tar.gz", hash = "sha256:b7593fe7eb5feaa3fbb461ac79aac9f9fc0387a5ca8080b0c6fe2ca27b091afd", size = 825862, upload-time = "2025-12-28T15:42:56.969Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2d/9a/3742e58fd04b233df95c012ee9f3dfe04708a5e1d32613bd2d47d4e1be0d/coverage-7.13.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e1fa280b3ad78eea5be86f94f461c04943d942697e0dac889fa18fff8f5f9147", size = 218633, upload-time = "2025-12-28T15:40:10.165Z" }, - { url = "https://files.pythonhosted.org/packages/7e/45/7e6bdc94d89cd7c8017ce735cf50478ddfe765d4fbf0c24d71d30ea33d7a/coverage-7.13.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c3d8c679607220979434f494b139dfb00131ebf70bb406553d69c1ff01a5c33d", size = 219147, upload-time = "2025-12-28T15:40:12.069Z" }, - { url = "https://files.pythonhosted.org/packages/f7/38/0d6a258625fd7f10773fe94097dc16937a5f0e3e0cdf3adef67d3ac6baef/coverage-7.13.1-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:339dc63b3eba969067b00f41f15ad161bf2946613156fb131266d8debc8e44d0", size = 245894, upload-time = "2025-12-28T15:40:13.556Z" }, - { url = "https://files.pythonhosted.org/packages/27/58/409d15ea487986994cbd4d06376e9860e9b157cfbfd402b1236770ab8dd2/coverage-7.13.1-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:db622b999ffe49cb891f2fff3b340cdc2f9797d01a0a202a0973ba2562501d90", size = 247721, upload-time = "2025-12-28T15:40:15.37Z" }, - { url = "https://files.pythonhosted.org/packages/da/bf/6e8056a83fd7a96c93341f1ffe10df636dd89f26d5e7b9ca511ce3bcf0df/coverage-7.13.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1443ba9acbb593fa7c1c29e011d7c9761545fe35e7652e85ce7f51a16f7e08d", size = 249585, upload-time = "2025-12-28T15:40:17.226Z" }, - { url = "https://files.pythonhosted.org/packages/f4/15/e1daff723f9f5959acb63cbe35b11203a9df77ee4b95b45fffd38b318390/coverage-7.13.1-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c832ec92c4499ac463186af72f9ed4d8daec15499b16f0a879b0d1c8e5cf4a3b", size = 246597, upload-time = "2025-12-28T15:40:19.028Z" }, - { url = "https://files.pythonhosted.org/packages/74/a6/1efd31c5433743a6ddbc9d37ac30c196bb07c7eab3d74fbb99b924c93174/coverage-7.13.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:562ec27dfa3f311e0db1ba243ec6e5f6ab96b1edfcfc6cf86f28038bc4961ce6", size = 247626, upload-time = "2025-12-28T15:40:20.846Z" }, - { url = "https://files.pythonhosted.org/packages/6d/9f/1609267dd3e749f57fdd66ca6752567d1c13b58a20a809dc409b263d0b5f/coverage-7.13.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:4de84e71173d4dada2897e5a0e1b7877e5eefbfe0d6a44edee6ce31d9b8ec09e", size = 245629, upload-time = "2025-12-28T15:40:22.397Z" }, - { url = "https://files.pythonhosted.org/packages/e2/f6/6815a220d5ec2466383d7cc36131b9fa6ecbe95c50ec52a631ba733f306a/coverage-7.13.1-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:a5a68357f686f8c4d527a2dc04f52e669c2fc1cbde38f6f7eb6a0e58cbd17cae", size = 245901, upload-time = "2025-12-28T15:40:23.836Z" }, - { url = "https://files.pythonhosted.org/packages/ac/58/40576554cd12e0872faf6d2c0eb3bc85f71d78427946ddd19ad65201e2c0/coverage-7.13.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:77cc258aeb29a3417062758975521eae60af6f79e930d6993555eeac6a8eac29", size = 246505, upload-time = "2025-12-28T15:40:25.421Z" }, - { url = "https://files.pythonhosted.org/packages/3b/77/9233a90253fba576b0eee81707b5781d0e21d97478e5377b226c5b096c0f/coverage-7.13.1-cp310-cp310-win32.whl", hash = "sha256:bb4f8c3c9a9f34423dba193f241f617b08ffc63e27f67159f60ae6baf2dcfe0f", size = 221257, upload-time = "2025-12-28T15:40:27.217Z" }, - { url = "https://files.pythonhosted.org/packages/e0/43/e842ff30c1a0a623ec80db89befb84a3a7aad7bfe44a6ea77d5a3e61fedd/coverage-7.13.1-cp310-cp310-win_amd64.whl", hash = "sha256:c8e2706ceb622bc63bac98ebb10ef5da80ed70fbd8a7999a5076de3afaef0fb1", size = 222191, upload-time = "2025-12-28T15:40:28.916Z" }, - { url = "https://files.pythonhosted.org/packages/b4/9b/77baf488516e9ced25fc215a6f75d803493fc3f6a1a1227ac35697910c2a/coverage-7.13.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1a55d509a1dc5a5b708b5dad3b5334e07a16ad4c2185e27b40e4dba796ab7f88", size = 218755, upload-time = "2025-12-28T15:40:30.812Z" }, - { url = "https://files.pythonhosted.org/packages/d7/cd/7ab01154e6eb79ee2fab76bf4d89e94c6648116557307ee4ebbb85e5c1bf/coverage-7.13.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4d010d080c4888371033baab27e47c9df7d6fb28d0b7b7adf85a4a49be9298b3", size = 219257, upload-time = "2025-12-28T15:40:32.333Z" }, - { url = "https://files.pythonhosted.org/packages/01/d5/b11ef7863ffbbdb509da0023fad1e9eda1c0eaea61a6d2ea5b17d4ac706e/coverage-7.13.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d938b4a840fb1523b9dfbbb454f652967f18e197569c32266d4d13f37244c3d9", size = 249657, upload-time = "2025-12-28T15:40:34.1Z" }, - { url = "https://files.pythonhosted.org/packages/f7/7c/347280982982383621d29b8c544cf497ae07ac41e44b1ca4903024131f55/coverage-7.13.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bf100a3288f9bb7f919b87eb84f87101e197535b9bd0e2c2b5b3179633324fee", size = 251581, upload-time = "2025-12-28T15:40:36.131Z" }, - { url = "https://files.pythonhosted.org/packages/82/f6/ebcfed11036ade4c0d75fa4453a6282bdd225bc073862766eec184a4c643/coverage-7.13.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef6688db9bf91ba111ae734ba6ef1a063304a881749726e0d3575f5c10a9facf", size = 253691, upload-time = "2025-12-28T15:40:37.626Z" }, - { url = "https://files.pythonhosted.org/packages/02/92/af8f5582787f5d1a8b130b2dcba785fa5e9a7a8e121a0bb2220a6fdbdb8a/coverage-7.13.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0b609fc9cdbd1f02e51f67f51e5aee60a841ef58a68d00d5ee2c0faf357481a3", size = 249799, upload-time = "2025-12-28T15:40:39.47Z" }, - { url = "https://files.pythonhosted.org/packages/24/aa/0e39a2a3b16eebf7f193863323edbff38b6daba711abaaf807d4290cf61a/coverage-7.13.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c43257717611ff5e9a1d79dce8e47566235ebda63328718d9b65dd640bc832ef", size = 251389, upload-time = "2025-12-28T15:40:40.954Z" }, - { url = "https://files.pythonhosted.org/packages/73/46/7f0c13111154dc5b978900c0ccee2e2ca239b910890e674a77f1363d483e/coverage-7.13.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e09fbecc007f7b6afdfb3b07ce5bd9f8494b6856dd4f577d26c66c391b829851", size = 249450, upload-time = "2025-12-28T15:40:42.489Z" }, - { url = "https://files.pythonhosted.org/packages/ac/ca/e80da6769e8b669ec3695598c58eef7ad98b0e26e66333996aee6316db23/coverage-7.13.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:a03a4f3a19a189919c7055098790285cc5c5b0b3976f8d227aea39dbf9f8bfdb", size = 249170, upload-time = "2025-12-28T15:40:44.279Z" }, - { url = "https://files.pythonhosted.org/packages/af/18/9e29baabdec1a8644157f572541079b4658199cfd372a578f84228e860de/coverage-7.13.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3820778ea1387c2b6a818caec01c63adc5b3750211af6447e8dcfb9b6f08dbba", size = 250081, upload-time = "2025-12-28T15:40:45.748Z" }, - { url = "https://files.pythonhosted.org/packages/00/f8/c3021625a71c3b2f516464d322e41636aea381018319050a8114105872ee/coverage-7.13.1-cp311-cp311-win32.whl", hash = "sha256:ff10896fa55167371960c5908150b434b71c876dfab97b69478f22c8b445ea19", size = 221281, upload-time = "2025-12-28T15:40:47.232Z" }, - { url = "https://files.pythonhosted.org/packages/27/56/c216625f453df6e0559ed666d246fcbaaa93f3aa99eaa5080cea1229aa3d/coverage-7.13.1-cp311-cp311-win_amd64.whl", hash = "sha256:a998cc0aeeea4c6d5622a3754da5a493055d2d95186bad877b0a34ea6e6dbe0a", size = 222215, upload-time = "2025-12-28T15:40:49.19Z" }, - { url = "https://files.pythonhosted.org/packages/5c/9a/be342e76f6e531cae6406dc46af0d350586f24d9b67fdfa6daee02df71af/coverage-7.13.1-cp311-cp311-win_arm64.whl", hash = "sha256:fea07c1a39a22614acb762e3fbbb4011f65eedafcb2948feeef641ac78b4ee5c", size = 220886, upload-time = "2025-12-28T15:40:51.067Z" }, - { url = "https://files.pythonhosted.org/packages/ce/8a/87af46cccdfa78f53db747b09f5f9a21d5fc38d796834adac09b30a8ce74/coverage-7.13.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6f34591000f06e62085b1865c9bc5f7858df748834662a51edadfd2c3bfe0dd3", size = 218927, upload-time = "2025-12-28T15:40:52.814Z" }, - { url = "https://files.pythonhosted.org/packages/82/a8/6e22fdc67242a4a5a153f9438d05944553121c8f4ba70cb072af4c41362e/coverage-7.13.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b67e47c5595b9224599016e333f5ec25392597a89d5744658f837d204e16c63e", size = 219288, upload-time = "2025-12-28T15:40:54.262Z" }, - { url = "https://files.pythonhosted.org/packages/d0/0a/853a76e03b0f7c4375e2ca025df45c918beb367f3e20a0a8e91967f6e96c/coverage-7.13.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3e7b8bd70c48ffb28461ebe092c2345536fb18bbbf19d287c8913699735f505c", size = 250786, upload-time = "2025-12-28T15:40:56.059Z" }, - { url = "https://files.pythonhosted.org/packages/ea/b4/694159c15c52b9f7ec7adf49d50e5f8ee71d3e9ef38adb4445d13dd56c20/coverage-7.13.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c223d078112e90dc0e5c4e35b98b9584164bea9fbbd221c0b21c5241f6d51b62", size = 253543, upload-time = "2025-12-28T15:40:57.585Z" }, - { url = "https://files.pythonhosted.org/packages/96/b2/7f1f0437a5c855f87e17cf5d0dc35920b6440ff2b58b1ba9788c059c26c8/coverage-7.13.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:794f7c05af0763b1bbd1b9e6eff0e52ad068be3b12cd96c87de037b01390c968", size = 254635, upload-time = "2025-12-28T15:40:59.443Z" }, - { url = "https://files.pythonhosted.org/packages/e9/d1/73c3fdb8d7d3bddd9473c9c6a2e0682f09fc3dfbcb9c3f36412a7368bcab/coverage-7.13.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0642eae483cc8c2902e4af7298bf886d605e80f26382124cddc3967c2a3df09e", size = 251202, upload-time = "2025-12-28T15:41:01.328Z" }, - { url = "https://files.pythonhosted.org/packages/66/3c/f0edf75dcc152f145d5598329e864bbbe04ab78660fe3e8e395f9fff010f/coverage-7.13.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9f5e772ed5fef25b3de9f2008fe67b92d46831bd2bc5bdc5dd6bfd06b83b316f", size = 252566, upload-time = "2025-12-28T15:41:03.319Z" }, - { url = "https://files.pythonhosted.org/packages/17/b3/e64206d3c5f7dcbceafd14941345a754d3dbc78a823a6ed526e23b9cdaab/coverage-7.13.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:45980ea19277dc0a579e432aef6a504fe098ef3a9032ead15e446eb0f1191aee", size = 250711, upload-time = "2025-12-28T15:41:06.411Z" }, - { url = "https://files.pythonhosted.org/packages/dc/ad/28a3eb970a8ef5b479ee7f0c484a19c34e277479a5b70269dc652b730733/coverage-7.13.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:e4f18eca6028ffa62adbd185a8f1e1dd242f2e68164dba5c2b74a5204850b4cf", size = 250278, upload-time = "2025-12-28T15:41:08.285Z" }, - { url = "https://files.pythonhosted.org/packages/54/e3/c8f0f1a93133e3e1291ca76cbb63565bd4b5c5df63b141f539d747fff348/coverage-7.13.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f8dca5590fec7a89ed6826fce625595279e586ead52e9e958d3237821fbc750c", size = 252154, upload-time = "2025-12-28T15:41:09.969Z" }, - { url = "https://files.pythonhosted.org/packages/d0/bf/9939c5d6859c380e405b19e736321f1c7d402728792f4c752ad1adcce005/coverage-7.13.1-cp312-cp312-win32.whl", hash = "sha256:ff86d4e85188bba72cfb876df3e11fa243439882c55957184af44a35bd5880b7", size = 221487, upload-time = "2025-12-28T15:41:11.468Z" }, - { url = "https://files.pythonhosted.org/packages/fa/dc/7282856a407c621c2aad74021680a01b23010bb8ebf427cf5eacda2e876f/coverage-7.13.1-cp312-cp312-win_amd64.whl", hash = "sha256:16cc1da46c04fb0fb128b4dc430b78fa2aba8a6c0c9f8eb391fd5103409a6ac6", size = 222299, upload-time = "2025-12-28T15:41:13.386Z" }, - { url = "https://files.pythonhosted.org/packages/10/79/176a11203412c350b3e9578620013af35bcdb79b651eb976f4a4b32044fa/coverage-7.13.1-cp312-cp312-win_arm64.whl", hash = "sha256:8d9bc218650022a768f3775dd7fdac1886437325d8d295d923ebcfef4892ad5c", size = 220941, upload-time = "2025-12-28T15:41:14.975Z" }, - { url = "https://files.pythonhosted.org/packages/a3/a4/e98e689347a1ff1a7f67932ab535cef82eb5e78f32a9e4132e114bbb3a0a/coverage-7.13.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:cb237bfd0ef4d5eb6a19e29f9e528ac67ac3be932ea6b44fb6cc09b9f3ecff78", size = 218951, upload-time = "2025-12-28T15:41:16.653Z" }, - { url = "https://files.pythonhosted.org/packages/32/33/7cbfe2bdc6e2f03d6b240d23dc45fdaf3fd270aaf2d640be77b7f16989ab/coverage-7.13.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1dcb645d7e34dcbcc96cd7c132b1fc55c39263ca62eb961c064eb3928997363b", size = 219325, upload-time = "2025-12-28T15:41:18.609Z" }, - { url = "https://files.pythonhosted.org/packages/59/f6/efdabdb4929487baeb7cb2a9f7dac457d9356f6ad1b255be283d58b16316/coverage-7.13.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3d42df8201e00384736f0df9be2ced39324c3907607d17d50d50116c989d84cd", size = 250309, upload-time = "2025-12-28T15:41:20.629Z" }, - { url = "https://files.pythonhosted.org/packages/12/da/91a52516e9d5aea87d32d1523f9cdcf7a35a3b298e6be05d6509ba3cfab2/coverage-7.13.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fa3edde1aa8807de1d05934982416cb3ec46d1d4d91e280bcce7cca01c507992", size = 252907, upload-time = "2025-12-28T15:41:22.257Z" }, - { url = "https://files.pythonhosted.org/packages/75/38/f1ea837e3dc1231e086db1638947e00d264e7e8c41aa8ecacf6e1e0c05f4/coverage-7.13.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9edd0e01a343766add6817bc448408858ba6b489039eaaa2018474e4001651a4", size = 254148, upload-time = "2025-12-28T15:41:23.87Z" }, - { url = "https://files.pythonhosted.org/packages/7f/43/f4f16b881aaa34954ba446318dea6b9ed5405dd725dd8daac2358eda869a/coverage-7.13.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:985b7836931d033570b94c94713c6dba5f9d3ff26045f72c3e5dbc5fe3361e5a", size = 250515, upload-time = "2025-12-28T15:41:25.437Z" }, - { url = "https://files.pythonhosted.org/packages/84/34/8cba7f00078bd468ea914134e0144263194ce849ec3baad187ffb6203d1c/coverage-7.13.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ffed1e4980889765c84a5d1a566159e363b71d6b6fbaf0bebc9d3c30bc016766", size = 252292, upload-time = "2025-12-28T15:41:28.459Z" }, - { url = "https://files.pythonhosted.org/packages/8c/a4/cffac66c7652d84ee4ac52d3ccb94c015687d3b513f9db04bfcac2ac800d/coverage-7.13.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8842af7f175078456b8b17f1b73a0d16a65dcbdc653ecefeb00a56b3c8c298c4", size = 250242, upload-time = "2025-12-28T15:41:30.02Z" }, - { url = "https://files.pythonhosted.org/packages/f4/78/9a64d462263dde416f3c0067efade7b52b52796f489b1037a95b0dc389c9/coverage-7.13.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:ccd7a6fca48ca9c131d9b0a2972a581e28b13416fc313fb98b6d24a03ce9a398", size = 250068, upload-time = "2025-12-28T15:41:32.007Z" }, - { url = "https://files.pythonhosted.org/packages/69/c8/a8994f5fece06db7c4a97c8fc1973684e178599b42e66280dded0524ef00/coverage-7.13.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0403f647055de2609be776965108447deb8e384fe4a553c119e3ff6bfbab4784", size = 251846, upload-time = "2025-12-28T15:41:33.946Z" }, - { url = "https://files.pythonhosted.org/packages/cc/f7/91fa73c4b80305c86598a2d4e54ba22df6bf7d0d97500944af7ef155d9f7/coverage-7.13.1-cp313-cp313-win32.whl", hash = "sha256:549d195116a1ba1e1ae2f5ca143f9777800f6636eab917d4f02b5310d6d73461", size = 221512, upload-time = "2025-12-28T15:41:35.519Z" }, - { url = "https://files.pythonhosted.org/packages/45/0b/0768b4231d5a044da8f75e097a8714ae1041246bb765d6b5563bab456735/coverage-7.13.1-cp313-cp313-win_amd64.whl", hash = "sha256:5899d28b5276f536fcf840b18b61a9fce23cc3aec1d114c44c07fe94ebeaa500", size = 222321, upload-time = "2025-12-28T15:41:37.371Z" }, - { url = "https://files.pythonhosted.org/packages/9b/b8/bdcb7253b7e85157282450262008f1366aa04663f3e3e4c30436f596c3e2/coverage-7.13.1-cp313-cp313-win_arm64.whl", hash = "sha256:868a2fae76dfb06e87291bcbd4dcbcc778a8500510b618d50496e520bd94d9b9", size = 220949, upload-time = "2025-12-28T15:41:39.553Z" }, - { url = "https://files.pythonhosted.org/packages/70/52/f2be52cc445ff75ea8397948c96c1b4ee14f7f9086ea62fc929c5ae7b717/coverage-7.13.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:67170979de0dacac3f3097d02b0ad188d8edcea44ccc44aaa0550af49150c7dc", size = 219643, upload-time = "2025-12-28T15:41:41.567Z" }, - { url = "https://files.pythonhosted.org/packages/47/79/c85e378eaa239e2edec0c5523f71542c7793fe3340954eafb0bc3904d32d/coverage-7.13.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f80e2bb21bfab56ed7405c2d79d34b5dc0bc96c2c1d2a067b643a09fb756c43a", size = 219997, upload-time = "2025-12-28T15:41:43.418Z" }, - { url = "https://files.pythonhosted.org/packages/fe/9b/b1ade8bfb653c0bbce2d6d6e90cc6c254cbb99b7248531cc76253cb4da6d/coverage-7.13.1-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f83351e0f7dcdb14d7326c3d8d8c4e915fa685cbfdc6281f9470d97a04e9dfe4", size = 261296, upload-time = "2025-12-28T15:41:45.207Z" }, - { url = "https://files.pythonhosted.org/packages/1f/af/ebf91e3e1a2473d523e87e87fd8581e0aa08741b96265730e2d79ce78d8d/coverage-7.13.1-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bb3f6562e89bad0110afbe64e485aac2462efdce6232cdec7862a095dc3412f6", size = 263363, upload-time = "2025-12-28T15:41:47.163Z" }, - { url = "https://files.pythonhosted.org/packages/c4/8b/fb2423526d446596624ac7fde12ea4262e66f86f5120114c3cfd0bb2befa/coverage-7.13.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:77545b5dcda13b70f872c3b5974ac64c21d05e65b1590b441c8560115dc3a0d1", size = 265783, upload-time = "2025-12-28T15:41:49.03Z" }, - { url = "https://files.pythonhosted.org/packages/9b/26/ef2adb1e22674913b89f0fe7490ecadcef4a71fa96f5ced90c60ec358789/coverage-7.13.1-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a4d240d260a1aed814790bbe1f10a5ff31ce6c21bc78f0da4a1e8268d6c80dbd", size = 260508, upload-time = "2025-12-28T15:41:51.035Z" }, - { url = "https://files.pythonhosted.org/packages/ce/7d/f0f59b3404caf662e7b5346247883887687c074ce67ba453ea08c612b1d5/coverage-7.13.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d2287ac9360dec3837bfdad969963a5d073a09a85d898bd86bea82aa8876ef3c", size = 263357, upload-time = "2025-12-28T15:41:52.631Z" }, - { url = "https://files.pythonhosted.org/packages/1a/b1/29896492b0b1a047604d35d6fa804f12818fa30cdad660763a5f3159e158/coverage-7.13.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:0d2c11f3ea4db66b5cbded23b20185c35066892c67d80ec4be4bab257b9ad1e0", size = 260978, upload-time = "2025-12-28T15:41:54.589Z" }, - { url = "https://files.pythonhosted.org/packages/48/f2/971de1238a62e6f0a4128d37adadc8bb882ee96afbe03ff1570291754629/coverage-7.13.1-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:3fc6a169517ca0d7ca6846c3c5392ef2b9e38896f61d615cb75b9e7134d4ee1e", size = 259877, upload-time = "2025-12-28T15:41:56.263Z" }, - { url = "https://files.pythonhosted.org/packages/6a/fc/0474efcbb590ff8628830e9aaec5f1831594874360e3251f1fdec31d07a3/coverage-7.13.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d10a2ed46386e850bb3de503a54f9fe8192e5917fcbb143bfef653a9355e9a53", size = 262069, upload-time = "2025-12-28T15:41:58.093Z" }, - { url = "https://files.pythonhosted.org/packages/88/4f/3c159b7953db37a7b44c0eab8a95c37d1aa4257c47b4602c04022d5cb975/coverage-7.13.1-cp313-cp313t-win32.whl", hash = "sha256:75a6f4aa904301dab8022397a22c0039edc1f51e90b83dbd4464b8a38dc87842", size = 222184, upload-time = "2025-12-28T15:41:59.763Z" }, - { url = "https://files.pythonhosted.org/packages/58/a5/6b57d28f81417f9335774f20679d9d13b9a8fb90cd6160957aa3b54a2379/coverage-7.13.1-cp313-cp313t-win_amd64.whl", hash = "sha256:309ef5706e95e62578cda256b97f5e097916a2c26247c287bbe74794e7150df2", size = 223250, upload-time = "2025-12-28T15:42:01.52Z" }, - { url = "https://files.pythonhosted.org/packages/81/7c/160796f3b035acfbb58be80e02e484548595aa67e16a6345e7910ace0a38/coverage-7.13.1-cp313-cp313t-win_arm64.whl", hash = "sha256:92f980729e79b5d16d221038dbf2e8f9a9136afa072f9d5d6ed4cb984b126a09", size = 221521, upload-time = "2025-12-28T15:42:03.275Z" }, - { url = "https://files.pythonhosted.org/packages/aa/8e/ba0e597560c6563fc0adb902fda6526df5d4aa73bb10adf0574d03bd2206/coverage-7.13.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:97ab3647280d458a1f9adb85244e81587505a43c0c7cff851f5116cd2814b894", size = 218996, upload-time = "2025-12-28T15:42:04.978Z" }, - { url = "https://files.pythonhosted.org/packages/6b/8e/764c6e116f4221dc7aa26c4061181ff92edb9c799adae6433d18eeba7a14/coverage-7.13.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8f572d989142e0908e6acf57ad1b9b86989ff057c006d13b76c146ec6a20216a", size = 219326, upload-time = "2025-12-28T15:42:06.691Z" }, - { url = "https://files.pythonhosted.org/packages/4f/a6/6130dc6d8da28cdcbb0f2bf8865aeca9b157622f7c0031e48c6cf9a0e591/coverage-7.13.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d72140ccf8a147e94274024ff6fd8fb7811354cf7ef88b1f0a988ebaa5bc774f", size = 250374, upload-time = "2025-12-28T15:42:08.786Z" }, - { url = "https://files.pythonhosted.org/packages/82/2b/783ded568f7cd6b677762f780ad338bf4b4750205860c17c25f7c708995e/coverage-7.13.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d3c9f051b028810f5a87c88e5d6e9af3c0ff32ef62763bf15d29f740453ca909", size = 252882, upload-time = "2025-12-28T15:42:10.515Z" }, - { url = "https://files.pythonhosted.org/packages/cd/b2/9808766d082e6a4d59eb0cc881a57fc1600eb2c5882813eefff8254f71b5/coverage-7.13.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f398ba4df52d30b1763f62eed9de5620dcde96e6f491f4c62686736b155aa6e4", size = 254218, upload-time = "2025-12-28T15:42:12.208Z" }, - { url = "https://files.pythonhosted.org/packages/44/ea/52a985bb447c871cb4d2e376e401116520991b597c85afdde1ea9ef54f2c/coverage-7.13.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:132718176cc723026d201e347f800cd1a9e4b62ccd3f82476950834dad501c75", size = 250391, upload-time = "2025-12-28T15:42:14.21Z" }, - { url = "https://files.pythonhosted.org/packages/7f/1d/125b36cc12310718873cfc8209ecfbc1008f14f4f5fa0662aa608e579353/coverage-7.13.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9e549d642426e3579b3f4b92d0431543b012dcb6e825c91619d4e93b7363c3f9", size = 252239, upload-time = "2025-12-28T15:42:16.292Z" }, - { url = "https://files.pythonhosted.org/packages/6a/16/10c1c164950cade470107f9f14bbac8485f8fb8515f515fca53d337e4a7f/coverage-7.13.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:90480b2134999301eea795b3a9dbf606c6fbab1b489150c501da84a959442465", size = 250196, upload-time = "2025-12-28T15:42:18.54Z" }, - { url = "https://files.pythonhosted.org/packages/2a/c6/cd860fac08780c6fd659732f6ced1b40b79c35977c1356344e44d72ba6c4/coverage-7.13.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e825dbb7f84dfa24663dd75835e7257f8882629fc11f03ecf77d84a75134b864", size = 250008, upload-time = "2025-12-28T15:42:20.365Z" }, - { url = "https://files.pythonhosted.org/packages/f0/3a/a8c58d3d38f82a5711e1e0a67268362af48e1a03df27c03072ac30feefcf/coverage-7.13.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:623dcc6d7a7ba450bbdbeedbaa0c42b329bdae16491af2282f12a7e809be7eb9", size = 251671, upload-time = "2025-12-28T15:42:22.114Z" }, - { url = "https://files.pythonhosted.org/packages/f0/bc/fd4c1da651d037a1e3d53e8cb3f8182f4b53271ffa9a95a2e211bacc0349/coverage-7.13.1-cp314-cp314-win32.whl", hash = "sha256:6e73ebb44dca5f708dc871fe0b90cf4cff1a13f9956f747cc87b535a840386f5", size = 221777, upload-time = "2025-12-28T15:42:23.919Z" }, - { url = "https://files.pythonhosted.org/packages/4b/50/71acabdc8948464c17e90b5ffd92358579bd0910732c2a1c9537d7536aa6/coverage-7.13.1-cp314-cp314-win_amd64.whl", hash = "sha256:be753b225d159feb397bd0bf91ae86f689bad0da09d3b301478cd39b878ab31a", size = 222592, upload-time = "2025-12-28T15:42:25.619Z" }, - { url = "https://files.pythonhosted.org/packages/f7/c8/a6fb943081bb0cc926499c7907731a6dc9efc2cbdc76d738c0ab752f1a32/coverage-7.13.1-cp314-cp314-win_arm64.whl", hash = "sha256:228b90f613b25ba0019361e4ab81520b343b622fc657daf7e501c4ed6a2366c0", size = 221169, upload-time = "2025-12-28T15:42:27.629Z" }, - { url = "https://files.pythonhosted.org/packages/16/61/d5b7a0a0e0e40d62e59bc8c7aa1afbd86280d82728ba97f0673b746b78e2/coverage-7.13.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:60cfb538fe9ef86e5b2ab0ca8fc8d62524777f6c611dcaf76dc16fbe9b8e698a", size = 219730, upload-time = "2025-12-28T15:42:29.306Z" }, - { url = "https://files.pythonhosted.org/packages/a3/2c/8881326445fd071bb49514d1ce97d18a46a980712b51fee84f9ab42845b4/coverage-7.13.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:57dfc8048c72ba48a8c45e188d811e5efd7e49b387effc8fb17e97936dde5bf6", size = 220001, upload-time = "2025-12-28T15:42:31.319Z" }, - { url = "https://files.pythonhosted.org/packages/b5/d7/50de63af51dfa3a7f91cc37ad8fcc1e244b734232fbc8b9ab0f3c834a5cd/coverage-7.13.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3f2f725aa3e909b3c5fdb8192490bdd8e1495e85906af74fe6e34a2a77ba0673", size = 261370, upload-time = "2025-12-28T15:42:32.992Z" }, - { url = "https://files.pythonhosted.org/packages/e1/2c/d31722f0ec918fd7453b2758312729f645978d212b410cd0f7c2aed88a94/coverage-7.13.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9ee68b21909686eeb21dfcba2c3b81fee70dcf38b140dcd5aa70680995fa3aa5", size = 263485, upload-time = "2025-12-28T15:42:34.759Z" }, - { url = "https://files.pythonhosted.org/packages/fa/7a/2c114fa5c5fc08ba0777e4aec4c97e0b4a1afcb69c75f1f54cff78b073ab/coverage-7.13.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:724b1b270cb13ea2e6503476e34541a0b1f62280bc997eab443f87790202033d", size = 265890, upload-time = "2025-12-28T15:42:36.517Z" }, - { url = "https://files.pythonhosted.org/packages/65/d9/f0794aa1c74ceabc780fe17f6c338456bbc4e96bd950f2e969f48ac6fb20/coverage-7.13.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:916abf1ac5cf7eb16bc540a5bf75c71c43a676f5c52fcb9fe75a2bd75fb944e8", size = 260445, upload-time = "2025-12-28T15:42:38.646Z" }, - { url = "https://files.pythonhosted.org/packages/49/23/184b22a00d9bb97488863ced9454068c79e413cb23f472da6cbddc6cfc52/coverage-7.13.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:776483fd35b58d8afe3acbd9988d5de592ab6da2d2a865edfdbc9fdb43e7c486", size = 263357, upload-time = "2025-12-28T15:42:40.788Z" }, - { url = "https://files.pythonhosted.org/packages/7d/bd/58af54c0c9199ea4190284f389005779d7daf7bf3ce40dcd2d2b2f96da69/coverage-7.13.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:b6f3b96617e9852703f5b633ea01315ca45c77e879584f283c44127f0f1ec564", size = 260959, upload-time = "2025-12-28T15:42:42.808Z" }, - { url = "https://files.pythonhosted.org/packages/4b/2a/6839294e8f78a4891bf1df79d69c536880ba2f970d0ff09e7513d6e352e9/coverage-7.13.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:bd63e7b74661fed317212fab774e2a648bc4bb09b35f25474f8e3325d2945cd7", size = 259792, upload-time = "2025-12-28T15:42:44.818Z" }, - { url = "https://files.pythonhosted.org/packages/ba/c3/528674d4623283310ad676c5af7414b9850ab6d55c2300e8aa4b945ec554/coverage-7.13.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:933082f161bbb3e9f90d00990dc956120f608cdbcaeea15c4d897f56ef4fe416", size = 262123, upload-time = "2025-12-28T15:42:47.108Z" }, - { url = "https://files.pythonhosted.org/packages/06/c5/8c0515692fb4c73ac379d8dc09b18eaf0214ecb76ea6e62467ba7a1556ff/coverage-7.13.1-cp314-cp314t-win32.whl", hash = "sha256:18be793c4c87de2965e1c0f060f03d9e5aff66cfeae8e1dbe6e5b88056ec153f", size = 222562, upload-time = "2025-12-28T15:42:49.144Z" }, - { url = "https://files.pythonhosted.org/packages/05/0e/c0a0c4678cb30dac735811db529b321d7e1c9120b79bd728d4f4d6b010e9/coverage-7.13.1-cp314-cp314t-win_amd64.whl", hash = "sha256:0e42e0ec0cd3e0d851cb3c91f770c9301f48647cb2877cb78f74bdaa07639a79", size = 223670, upload-time = "2025-12-28T15:42:51.218Z" }, - { url = "https://files.pythonhosted.org/packages/f5/5f/b177aa0011f354abf03a8f30a85032686d290fdeed4222b27d36b4372a50/coverage-7.13.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eaecf47ef10c72ece9a2a92118257da87e460e113b83cc0d2905cbbe931792b4", size = 221707, upload-time = "2025-12-28T15:42:53.034Z" }, - { url = "https://files.pythonhosted.org/packages/cc/48/d9f421cb8da5afaa1a64570d9989e00fb7955e6acddc5a12979f7666ef60/coverage-7.13.1-py3-none-any.whl", hash = "sha256:2016745cb3ba554469d02819d78958b571792bb68e31302610e898f80dd3a573", size = 210722, upload-time = "2025-12-28T15:42:54.901Z" }, +version = "7.12.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/89/26/4a96807b193b011588099c3b5c89fbb05294e5b90e71018e065465f34eb6/coverage-7.12.0.tar.gz", hash = "sha256:fc11e0a4e372cb5f282f16ef90d4a585034050ccda536451901abfb19a57f40c", size = 819341, upload-time = "2025-11-18T13:34:20.766Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/4a/0dc3de1c172d35abe512332cfdcc43211b6ebce629e4cc42e6cd25ed8f4d/coverage-7.12.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:32b75c2ba3f324ee37af3ccee5b30458038c50b349ad9b88cee85096132a575b", size = 217409, upload-time = "2025-11-18T13:31:53.122Z" }, + { url = "https://files.pythonhosted.org/packages/01/c3/086198b98db0109ad4f84241e8e9ea7e5fb2db8c8ffb787162d40c26cc76/coverage-7.12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cb2a1b6ab9fe833714a483a915de350abc624a37149649297624c8d57add089c", size = 217927, upload-time = "2025-11-18T13:31:54.458Z" }, + { url = "https://files.pythonhosted.org/packages/5d/5f/34614dbf5ce0420828fc6c6f915126a0fcb01e25d16cf141bf5361e6aea6/coverage-7.12.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5734b5d913c3755e72f70bf6cc37a0518d4f4745cde760c5d8e12005e62f9832", size = 244678, upload-time = "2025-11-18T13:31:55.805Z" }, + { url = "https://files.pythonhosted.org/packages/55/7b/6b26fb32e8e4a6989ac1d40c4e132b14556131493b1d06bc0f2be169c357/coverage-7.12.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b527a08cdf15753279b7afb2339a12073620b761d79b81cbe2cdebdb43d90daa", size = 246507, upload-time = "2025-11-18T13:31:57.05Z" }, + { url = "https://files.pythonhosted.org/packages/06/42/7d70e6603d3260199b90fb48b537ca29ac183d524a65cc31366b2e905fad/coverage-7.12.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9bb44c889fb68004e94cab71f6a021ec83eac9aeabdbb5a5a88821ec46e1da73", size = 248366, upload-time = "2025-11-18T13:31:58.362Z" }, + { url = "https://files.pythonhosted.org/packages/2d/4a/d86b837923878424c72458c5b25e899a3c5ca73e663082a915f5b3c4d749/coverage-7.12.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4b59b501455535e2e5dde5881739897967b272ba25988c89145c12d772810ccb", size = 245366, upload-time = "2025-11-18T13:31:59.572Z" }, + { url = "https://files.pythonhosted.org/packages/e6/c2/2adec557e0aa9721875f06ced19730fdb7fc58e31b02b5aa56f2ebe4944d/coverage-7.12.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d8842f17095b9868a05837b7b1b73495293091bed870e099521ada176aa3e00e", size = 246408, upload-time = "2025-11-18T13:32:00.784Z" }, + { url = "https://files.pythonhosted.org/packages/5a/4b/8bd1f1148260df11c618e535fdccd1e5aaf646e55b50759006a4f41d8a26/coverage-7.12.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:c5a6f20bf48b8866095c6820641e7ffbe23f2ac84a2efc218d91235e404c7777", size = 244416, upload-time = "2025-11-18T13:32:01.963Z" }, + { url = "https://files.pythonhosted.org/packages/0e/13/3a248dd6a83df90414c54a4e121fd081fb20602ca43955fbe1d60e2312a9/coverage-7.12.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:5f3738279524e988d9da2893f307c2093815c623f8d05a8f79e3eff3a7a9e553", size = 244681, upload-time = "2025-11-18T13:32:03.408Z" }, + { url = "https://files.pythonhosted.org/packages/76/30/aa833827465a5e8c938935f5d91ba055f70516941078a703740aaf1aa41f/coverage-7.12.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e0d68c1f7eabbc8abe582d11fa393ea483caf4f44b0af86881174769f185c94d", size = 245300, upload-time = "2025-11-18T13:32:04.686Z" }, + { url = "https://files.pythonhosted.org/packages/38/24/f85b3843af1370fb3739fa7571819b71243daa311289b31214fe3e8c9d68/coverage-7.12.0-cp310-cp310-win32.whl", hash = "sha256:7670d860e18b1e3ee5930b17a7d55ae6287ec6e55d9799982aa103a2cc1fa2ef", size = 220008, upload-time = "2025-11-18T13:32:05.806Z" }, + { url = "https://files.pythonhosted.org/packages/3a/a2/c7da5b9566f7164db9eefa133d17761ecb2c2fde9385d754e5b5c80f710d/coverage-7.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:f999813dddeb2a56aab5841e687b68169da0d3f6fc78ccf50952fa2463746022", size = 220943, upload-time = "2025-11-18T13:32:07.166Z" }, + { url = "https://files.pythonhosted.org/packages/5a/0c/0dfe7f0487477d96432e4815537263363fb6dd7289743a796e8e51eabdf2/coverage-7.12.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aa124a3683d2af98bd9d9c2bfa7a5076ca7e5ab09fdb96b81fa7d89376ae928f", size = 217535, upload-time = "2025-11-18T13:32:08.812Z" }, + { url = "https://files.pythonhosted.org/packages/9b/f5/f9a4a053a5bbff023d3bec259faac8f11a1e5a6479c2ccf586f910d8dac7/coverage-7.12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d93fbf446c31c0140208dcd07c5d882029832e8ed7891a39d6d44bd65f2316c3", size = 218044, upload-time = "2025-11-18T13:32:10.329Z" }, + { url = "https://files.pythonhosted.org/packages/95/c5/84fc3697c1fa10cd8571919bf9693f693b7373278daaf3b73e328d502bc8/coverage-7.12.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:52ca620260bd8cd6027317bdd8b8ba929be1d741764ee765b42c4d79a408601e", size = 248440, upload-time = "2025-11-18T13:32:12.536Z" }, + { url = "https://files.pythonhosted.org/packages/f4/36/2d93fbf6a04670f3874aed397d5a5371948a076e3249244a9e84fb0e02d6/coverage-7.12.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f3433ffd541380f3a0e423cff0f4926d55b0cc8c1d160fdc3be24a4c03aa65f7", size = 250361, upload-time = "2025-11-18T13:32:13.852Z" }, + { url = "https://files.pythonhosted.org/packages/5d/49/66dc65cc456a6bfc41ea3d0758c4afeaa4068a2b2931bf83be6894cf1058/coverage-7.12.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f7bbb321d4adc9f65e402c677cd1c8e4c2d0105d3ce285b51b4d87f1d5db5245", size = 252472, upload-time = "2025-11-18T13:32:15.068Z" }, + { url = "https://files.pythonhosted.org/packages/35/1f/ebb8a18dffd406db9fcd4b3ae42254aedcaf612470e8712f12041325930f/coverage-7.12.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:22a7aade354a72dff3b59c577bfd18d6945c61f97393bc5fb7bd293a4237024b", size = 248592, upload-time = "2025-11-18T13:32:16.328Z" }, + { url = "https://files.pythonhosted.org/packages/da/a8/67f213c06e5ea3b3d4980df7dc344d7fea88240b5fe878a5dcbdfe0e2315/coverage-7.12.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3ff651dcd36d2fea66877cd4a82de478004c59b849945446acb5baf9379a1b64", size = 250167, upload-time = "2025-11-18T13:32:17.687Z" }, + { url = "https://files.pythonhosted.org/packages/f0/00/e52aef68154164ea40cc8389c120c314c747fe63a04b013a5782e989b77f/coverage-7.12.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:31b8b2e38391a56e3cea39d22a23faaa7c3fc911751756ef6d2621d2a9daf742", size = 248238, upload-time = "2025-11-18T13:32:19.2Z" }, + { url = "https://files.pythonhosted.org/packages/1f/a4/4d88750bcf9d6d66f77865e5a05a20e14db44074c25fd22519777cb69025/coverage-7.12.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:297bc2da28440f5ae51c845a47c8175a4db0553a53827886e4fb25c66633000c", size = 247964, upload-time = "2025-11-18T13:32:21.027Z" }, + { url = "https://files.pythonhosted.org/packages/a7/6b/b74693158899d5b47b0bf6238d2c6722e20ba749f86b74454fac0696bb00/coverage-7.12.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6ff7651cc01a246908eac162a6a86fc0dbab6de1ad165dfb9a1e2ec660b44984", size = 248862, upload-time = "2025-11-18T13:32:22.304Z" }, + { url = "https://files.pythonhosted.org/packages/18/de/6af6730227ce0e8ade307b1cc4a08e7f51b419a78d02083a86c04ccceb29/coverage-7.12.0-cp311-cp311-win32.whl", hash = "sha256:313672140638b6ddb2c6455ddeda41c6a0b208298034544cfca138978c6baed6", size = 220033, upload-time = "2025-11-18T13:32:23.714Z" }, + { url = "https://files.pythonhosted.org/packages/e2/a1/e7f63021a7c4fe20994359fcdeae43cbef4a4d0ca36a5a1639feeea5d9e1/coverage-7.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:a1783ed5bd0d5938d4435014626568dc7f93e3cb99bc59188cc18857c47aa3c4", size = 220966, upload-time = "2025-11-18T13:32:25.599Z" }, + { url = "https://files.pythonhosted.org/packages/77/e8/deae26453f37c20c3aa0c4433a1e32cdc169bf415cce223a693117aa3ddd/coverage-7.12.0-cp311-cp311-win_arm64.whl", hash = "sha256:4648158fd8dd9381b5847622df1c90ff314efbfc1df4550092ab6013c238a5fc", size = 219637, upload-time = "2025-11-18T13:32:27.265Z" }, + { url = "https://files.pythonhosted.org/packages/02/bf/638c0427c0f0d47638242e2438127f3c8ee3cfc06c7fdeb16778ed47f836/coverage-7.12.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:29644c928772c78512b48e14156b81255000dcfd4817574ff69def189bcb3647", size = 217704, upload-time = "2025-11-18T13:32:28.906Z" }, + { url = "https://files.pythonhosted.org/packages/08/e1/706fae6692a66c2d6b871a608bbde0da6281903fa0e9f53a39ed441da36a/coverage-7.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8638cbb002eaa5d7c8d04da667813ce1067080b9a91099801a0053086e52b736", size = 218064, upload-time = "2025-11-18T13:32:30.161Z" }, + { url = "https://files.pythonhosted.org/packages/a9/8b/eb0231d0540f8af3ffda39720ff43cb91926489d01524e68f60e961366e4/coverage-7.12.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:083631eeff5eb9992c923e14b810a179798bb598e6a0dd60586819fc23be6e60", size = 249560, upload-time = "2025-11-18T13:32:31.835Z" }, + { url = "https://files.pythonhosted.org/packages/e9/a1/67fb52af642e974d159b5b379e4d4c59d0ebe1288677fbd04bbffe665a82/coverage-7.12.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:99d5415c73ca12d558e07776bd957c4222c687b9f1d26fa0e1b57e3598bdcde8", size = 252318, upload-time = "2025-11-18T13:32:33.178Z" }, + { url = "https://files.pythonhosted.org/packages/41/e5/38228f31b2c7665ebf9bdfdddd7a184d56450755c7e43ac721c11a4b8dab/coverage-7.12.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e949ebf60c717c3df63adb4a1a366c096c8d7fd8472608cd09359e1bd48ef59f", size = 253403, upload-time = "2025-11-18T13:32:34.45Z" }, + { url = "https://files.pythonhosted.org/packages/ec/4b/df78e4c8188f9960684267c5a4897836f3f0f20a20c51606ee778a1d9749/coverage-7.12.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6d907ddccbca819afa2cd014bc69983b146cca2735a0b1e6259b2a6c10be1e70", size = 249984, upload-time = "2025-11-18T13:32:35.747Z" }, + { url = "https://files.pythonhosted.org/packages/ba/51/bb163933d195a345c6f63eab9e55743413d064c291b6220df754075c2769/coverage-7.12.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b1518ecbad4e6173f4c6e6c4a46e49555ea5679bf3feda5edb1b935c7c44e8a0", size = 251339, upload-time = "2025-11-18T13:32:37.352Z" }, + { url = "https://files.pythonhosted.org/packages/15/40/c9b29cdb8412c837cdcbc2cfa054547dd83affe6cbbd4ce4fdb92b6ba7d1/coverage-7.12.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:51777647a749abdf6f6fd8c7cffab12de68ab93aab15efc72fbbb83036c2a068", size = 249489, upload-time = "2025-11-18T13:32:39.212Z" }, + { url = "https://files.pythonhosted.org/packages/c8/da/b3131e20ba07a0de4437a50ef3b47840dfabf9293675b0cd5c2c7f66dd61/coverage-7.12.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:42435d46d6461a3b305cdfcad7cdd3248787771f53fe18305548cba474e6523b", size = 249070, upload-time = "2025-11-18T13:32:40.598Z" }, + { url = "https://files.pythonhosted.org/packages/70/81/b653329b5f6302c08d683ceff6785bc60a34be9ae92a5c7b63ee7ee7acec/coverage-7.12.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5bcead88c8423e1855e64b8057d0544e33e4080b95b240c2a355334bb7ced937", size = 250929, upload-time = "2025-11-18T13:32:42.915Z" }, + { url = "https://files.pythonhosted.org/packages/a3/00/250ac3bca9f252a5fb1338b5ad01331ebb7b40223f72bef5b1b2cb03aa64/coverage-7.12.0-cp312-cp312-win32.whl", hash = "sha256:dcbb630ab034e86d2a0f79aefd2be07e583202f41e037602d438c80044957baa", size = 220241, upload-time = "2025-11-18T13:32:44.665Z" }, + { url = "https://files.pythonhosted.org/packages/64/1c/77e79e76d37ce83302f6c21980b45e09f8aa4551965213a10e62d71ce0ab/coverage-7.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:2fd8354ed5d69775ac42986a691fbf68b4084278710cee9d7c3eaa0c28fa982a", size = 221051, upload-time = "2025-11-18T13:32:46.008Z" }, + { url = "https://files.pythonhosted.org/packages/31/f5/641b8a25baae564f9e52cac0e2667b123de961985709a004e287ee7663cc/coverage-7.12.0-cp312-cp312-win_arm64.whl", hash = "sha256:737c3814903be30695b2de20d22bcc5428fdae305c61ba44cdc8b3252984c49c", size = 219692, upload-time = "2025-11-18T13:32:47.372Z" }, + { url = "https://files.pythonhosted.org/packages/b8/14/771700b4048774e48d2c54ed0c674273702713c9ee7acdfede40c2666747/coverage-7.12.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:47324fffca8d8eae7e185b5bb20c14645f23350f870c1649003618ea91a78941", size = 217725, upload-time = "2025-11-18T13:32:49.22Z" }, + { url = "https://files.pythonhosted.org/packages/17/a7/3aa4144d3bcb719bf67b22d2d51c2d577bf801498c13cb08f64173e80497/coverage-7.12.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ccf3b2ede91decd2fb53ec73c1f949c3e034129d1e0b07798ff1d02ea0c8fa4a", size = 218098, upload-time = "2025-11-18T13:32:50.78Z" }, + { url = "https://files.pythonhosted.org/packages/fc/9c/b846bbc774ff81091a12a10203e70562c91ae71badda00c5ae5b613527b1/coverage-7.12.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:b365adc70a6936c6b0582dc38746b33b2454148c02349345412c6e743efb646d", size = 249093, upload-time = "2025-11-18T13:32:52.554Z" }, + { url = "https://files.pythonhosted.org/packages/76/b6/67d7c0e1f400b32c883e9342de4a8c2ae7c1a0b57c5de87622b7262e2309/coverage-7.12.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bc13baf85cd8a4cfcf4a35c7bc9d795837ad809775f782f697bf630b7e200211", size = 251686, upload-time = "2025-11-18T13:32:54.862Z" }, + { url = "https://files.pythonhosted.org/packages/cc/75/b095bd4b39d49c3be4bffbb3135fea18a99a431c52dd7513637c0762fecb/coverage-7.12.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:099d11698385d572ceafb3288a5b80fe1fc58bf665b3f9d362389de488361d3d", size = 252930, upload-time = "2025-11-18T13:32:56.417Z" }, + { url = "https://files.pythonhosted.org/packages/6e/f3/466f63015c7c80550bead3093aacabf5380c1220a2a93c35d374cae8f762/coverage-7.12.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:473dc45d69694069adb7680c405fb1e81f60b2aff42c81e2f2c3feaf544d878c", size = 249296, upload-time = "2025-11-18T13:32:58.074Z" }, + { url = "https://files.pythonhosted.org/packages/27/86/eba2209bf2b7e28c68698fc13437519a295b2d228ba9e0ec91673e09fa92/coverage-7.12.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:583f9adbefd278e9de33c33d6846aa8f5d164fa49b47144180a0e037f0688bb9", size = 251068, upload-time = "2025-11-18T13:32:59.646Z" }, + { url = "https://files.pythonhosted.org/packages/ec/55/ca8ae7dbba962a3351f18940b359b94c6bafdd7757945fdc79ec9e452dc7/coverage-7.12.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b2089cc445f2dc0af6f801f0d1355c025b76c24481935303cf1af28f636688f0", size = 249034, upload-time = "2025-11-18T13:33:01.481Z" }, + { url = "https://files.pythonhosted.org/packages/7a/d7/39136149325cad92d420b023b5fd900dabdd1c3a0d1d5f148ef4a8cedef5/coverage-7.12.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:950411f1eb5d579999c5f66c62a40961f126fc71e5e14419f004471957b51508", size = 248853, upload-time = "2025-11-18T13:33:02.935Z" }, + { url = "https://files.pythonhosted.org/packages/fe/b6/76e1add8b87ef60e00643b0b7f8f7bb73d4bf5249a3be19ebefc5793dd25/coverage-7.12.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b1aab7302a87bafebfe76b12af681b56ff446dc6f32ed178ff9c092ca776e6bc", size = 250619, upload-time = "2025-11-18T13:33:04.336Z" }, + { url = "https://files.pythonhosted.org/packages/95/87/924c6dc64f9203f7a3c1832a6a0eee5a8335dbe5f1bdadcc278d6f1b4d74/coverage-7.12.0-cp313-cp313-win32.whl", hash = "sha256:d7e0d0303c13b54db495eb636bc2465b2fb8475d4c8bcec8fe4b5ca454dfbae8", size = 220261, upload-time = "2025-11-18T13:33:06.493Z" }, + { url = "https://files.pythonhosted.org/packages/91/77/dd4aff9af16ff776bf355a24d87eeb48fc6acde54c907cc1ea89b14a8804/coverage-7.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:ce61969812d6a98a981d147d9ac583a36ac7db7766f2e64a9d4d059c2fe29d07", size = 221072, upload-time = "2025-11-18T13:33:07.926Z" }, + { url = "https://files.pythonhosted.org/packages/70/49/5c9dc46205fef31b1b226a6e16513193715290584317fd4df91cdaf28b22/coverage-7.12.0-cp313-cp313-win_arm64.whl", hash = "sha256:bcec6f47e4cb8a4c2dc91ce507f6eefc6a1b10f58df32cdc61dff65455031dfc", size = 219702, upload-time = "2025-11-18T13:33:09.631Z" }, + { url = "https://files.pythonhosted.org/packages/9b/62/f87922641c7198667994dd472a91e1d9b829c95d6c29529ceb52132436ad/coverage-7.12.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:459443346509476170d553035e4a3eed7b860f4fe5242f02de1010501956ce87", size = 218420, upload-time = "2025-11-18T13:33:11.153Z" }, + { url = "https://files.pythonhosted.org/packages/85/dd/1cc13b2395ef15dbb27d7370a2509b4aee77890a464fb35d72d428f84871/coverage-7.12.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:04a79245ab2b7a61688958f7a855275997134bc84f4a03bc240cf64ff132abf6", size = 218773, upload-time = "2025-11-18T13:33:12.569Z" }, + { url = "https://files.pythonhosted.org/packages/74/40/35773cc4bb1e9d4658d4fb669eb4195b3151bef3bbd6f866aba5cd5dac82/coverage-7.12.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:09a86acaaa8455f13d6a99221d9654df249b33937b4e212b4e5a822065f12aa7", size = 260078, upload-time = "2025-11-18T13:33:14.037Z" }, + { url = "https://files.pythonhosted.org/packages/ec/ee/231bb1a6ffc2905e396557585ebc6bdc559e7c66708376d245a1f1d330fc/coverage-7.12.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:907e0df1b71ba77463687a74149c6122c3f6aac56c2510a5d906b2f368208560", size = 262144, upload-time = "2025-11-18T13:33:15.601Z" }, + { url = "https://files.pythonhosted.org/packages/28/be/32f4aa9f3bf0b56f3971001b56508352c7753915345d45fab4296a986f01/coverage-7.12.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9b57e2d0ddd5f0582bae5437c04ee71c46cd908e7bc5d4d0391f9a41e812dd12", size = 264574, upload-time = "2025-11-18T13:33:17.354Z" }, + { url = "https://files.pythonhosted.org/packages/68/7c/00489fcbc2245d13ab12189b977e0cf06ff3351cb98bc6beba8bd68c5902/coverage-7.12.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:58c1c6aa677f3a1411fe6fb28ec3a942e4f665df036a3608816e0847fad23296", size = 259298, upload-time = "2025-11-18T13:33:18.958Z" }, + { url = "https://files.pythonhosted.org/packages/96/b4/f0760d65d56c3bea95b449e02570d4abd2549dc784bf39a2d4721a2d8ceb/coverage-7.12.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4c589361263ab2953e3c4cd2a94db94c4ad4a8e572776ecfbad2389c626e4507", size = 262150, upload-time = "2025-11-18T13:33:20.644Z" }, + { url = "https://files.pythonhosted.org/packages/c5/71/9a9314df00f9326d78c1e5a910f520d599205907432d90d1c1b7a97aa4b1/coverage-7.12.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:91b810a163ccad2e43b1faa11d70d3cf4b6f3d83f9fd5f2df82a32d47b648e0d", size = 259763, upload-time = "2025-11-18T13:33:22.189Z" }, + { url = "https://files.pythonhosted.org/packages/10/34/01a0aceed13fbdf925876b9a15d50862eb8845454301fe3cdd1df08b2182/coverage-7.12.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:40c867af715f22592e0d0fb533a33a71ec9e0f73a6945f722a0c85c8c1cbe3a2", size = 258653, upload-time = "2025-11-18T13:33:24.239Z" }, + { url = "https://files.pythonhosted.org/packages/8d/04/81d8fd64928acf1574bbb0181f66901c6c1c6279c8ccf5f84259d2c68ae9/coverage-7.12.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:68b0d0a2d84f333de875666259dadf28cc67858bc8fd8b3f1eae84d3c2bec455", size = 260856, upload-time = "2025-11-18T13:33:26.365Z" }, + { url = "https://files.pythonhosted.org/packages/f2/76/fa2a37bfaeaf1f766a2d2360a25a5297d4fb567098112f6517475eee120b/coverage-7.12.0-cp313-cp313t-win32.whl", hash = "sha256:73f9e7fbd51a221818fd11b7090eaa835a353ddd59c236c57b2199486b116c6d", size = 220936, upload-time = "2025-11-18T13:33:28.165Z" }, + { url = "https://files.pythonhosted.org/packages/f9/52/60f64d932d555102611c366afb0eb434b34266b1d9266fc2fe18ab641c47/coverage-7.12.0-cp313-cp313t-win_amd64.whl", hash = "sha256:24cff9d1f5743f67db7ba46ff284018a6e9aeb649b67aa1e70c396aa1b7cb23c", size = 222001, upload-time = "2025-11-18T13:33:29.656Z" }, + { url = "https://files.pythonhosted.org/packages/77/df/c303164154a5a3aea7472bf323b7c857fed93b26618ed9fc5c2955566bb0/coverage-7.12.0-cp313-cp313t-win_arm64.whl", hash = "sha256:c87395744f5c77c866d0f5a43d97cc39e17c7f1cb0115e54a2fe67ca75c5d14d", size = 220273, upload-time = "2025-11-18T13:33:31.415Z" }, + { url = "https://files.pythonhosted.org/packages/bf/2e/fc12db0883478d6e12bbd62d481210f0c8daf036102aa11434a0c5755825/coverage-7.12.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:a1c59b7dc169809a88b21a936eccf71c3895a78f5592051b1af8f4d59c2b4f92", size = 217777, upload-time = "2025-11-18T13:33:32.86Z" }, + { url = "https://files.pythonhosted.org/packages/1f/c1/ce3e525d223350c6ec16b9be8a057623f54226ef7f4c2fee361ebb6a02b8/coverage-7.12.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8787b0f982e020adb732b9f051f3e49dd5054cebbc3f3432061278512a2b1360", size = 218100, upload-time = "2025-11-18T13:33:34.532Z" }, + { url = "https://files.pythonhosted.org/packages/15/87/113757441504aee3808cb422990ed7c8bcc2d53a6779c66c5adef0942939/coverage-7.12.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5ea5a9f7dc8877455b13dd1effd3202e0bca72f6f3ab09f9036b1bcf728f69ac", size = 249151, upload-time = "2025-11-18T13:33:36.135Z" }, + { url = "https://files.pythonhosted.org/packages/d9/1d/9529d9bd44049b6b05bb319c03a3a7e4b0a8a802d28fa348ad407e10706d/coverage-7.12.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fdba9f15849534594f60b47c9a30bc70409b54947319a7c4fd0e8e3d8d2f355d", size = 251667, upload-time = "2025-11-18T13:33:37.996Z" }, + { url = "https://files.pythonhosted.org/packages/11/bb/567e751c41e9c03dc29d3ce74b8c89a1e3396313e34f255a2a2e8b9ebb56/coverage-7.12.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a00594770eb715854fb1c57e0dea08cce6720cfbc531accdb9850d7c7770396c", size = 253003, upload-time = "2025-11-18T13:33:39.553Z" }, + { url = "https://files.pythonhosted.org/packages/e4/b3/c2cce2d8526a02fb9e9ca14a263ca6fc074449b33a6afa4892838c903528/coverage-7.12.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5560c7e0d82b42eb1951e4f68f071f8017c824ebfd5a6ebe42c60ac16c6c2434", size = 249185, upload-time = "2025-11-18T13:33:42.086Z" }, + { url = "https://files.pythonhosted.org/packages/0e/a7/967f93bb66e82c9113c66a8d0b65ecf72fc865adfba5a145f50c7af7e58d/coverage-7.12.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d6c2e26b481c9159c2773a37947a9718cfdc58893029cdfb177531793e375cfc", size = 251025, upload-time = "2025-11-18T13:33:43.634Z" }, + { url = "https://files.pythonhosted.org/packages/b9/b2/f2f6f56337bc1af465d5b2dc1ee7ee2141b8b9272f3bf6213fcbc309a836/coverage-7.12.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:6e1a8c066dabcde56d5d9fed6a66bc19a2883a3fe051f0c397a41fc42aedd4cc", size = 248979, upload-time = "2025-11-18T13:33:46.04Z" }, + { url = "https://files.pythonhosted.org/packages/f4/7a/bf4209f45a4aec09d10a01a57313a46c0e0e8f4c55ff2965467d41a92036/coverage-7.12.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:f7ba9da4726e446d8dd8aae5a6cd872511184a5d861de80a86ef970b5dacce3e", size = 248800, upload-time = "2025-11-18T13:33:47.546Z" }, + { url = "https://files.pythonhosted.org/packages/b8/b7/1e01b8696fb0521810f60c5bbebf699100d6754183e6cc0679bf2ed76531/coverage-7.12.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e0f483ab4f749039894abaf80c2f9e7ed77bbf3c737517fb88c8e8e305896a17", size = 250460, upload-time = "2025-11-18T13:33:49.537Z" }, + { url = "https://files.pythonhosted.org/packages/71/ae/84324fb9cb46c024760e706353d9b771a81b398d117d8c1fe010391c186f/coverage-7.12.0-cp314-cp314-win32.whl", hash = "sha256:76336c19a9ef4a94b2f8dc79f8ac2da3f193f625bb5d6f51a328cd19bfc19933", size = 220533, upload-time = "2025-11-18T13:33:51.16Z" }, + { url = "https://files.pythonhosted.org/packages/e2/71/1033629deb8460a8f97f83e6ac4ca3b93952e2b6f826056684df8275e015/coverage-7.12.0-cp314-cp314-win_amd64.whl", hash = "sha256:7c1059b600aec6ef090721f8f633f60ed70afaffe8ecab85b59df748f24b31fe", size = 221348, upload-time = "2025-11-18T13:33:52.776Z" }, + { url = "https://files.pythonhosted.org/packages/0a/5f/ac8107a902f623b0c251abdb749be282dc2ab61854a8a4fcf49e276fce2f/coverage-7.12.0-cp314-cp314-win_arm64.whl", hash = "sha256:172cf3a34bfef42611963e2b661302a8931f44df31629e5b1050567d6b90287d", size = 219922, upload-time = "2025-11-18T13:33:54.316Z" }, + { url = "https://files.pythonhosted.org/packages/79/6e/f27af2d4da367f16077d21ef6fe796c874408219fa6dd3f3efe7751bd910/coverage-7.12.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:aa7d48520a32cb21c7a9b31f81799e8eaec7239db36c3b670be0fa2403828d1d", size = 218511, upload-time = "2025-11-18T13:33:56.343Z" }, + { url = "https://files.pythonhosted.org/packages/67/dd/65fd874aa460c30da78f9d259400d8e6a4ef457d61ab052fd248f0050558/coverage-7.12.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:90d58ac63bc85e0fb919f14d09d6caa63f35a5512a2205284b7816cafd21bb03", size = 218771, upload-time = "2025-11-18T13:33:57.966Z" }, + { url = "https://files.pythonhosted.org/packages/55/e0/7c6b71d327d8068cb79c05f8f45bf1b6145f7a0de23bbebe63578fe5240a/coverage-7.12.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ca8ecfa283764fdda3eae1bdb6afe58bf78c2c3ec2b2edcb05a671f0bba7b3f9", size = 260151, upload-time = "2025-11-18T13:33:59.597Z" }, + { url = "https://files.pythonhosted.org/packages/49/ce/4697457d58285b7200de6b46d606ea71066c6e674571a946a6ea908fb588/coverage-7.12.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:874fe69a0785d96bd066059cd4368022cebbec1a8958f224f0016979183916e6", size = 262257, upload-time = "2025-11-18T13:34:01.166Z" }, + { url = "https://files.pythonhosted.org/packages/2f/33/acbc6e447aee4ceba88c15528dbe04a35fb4d67b59d393d2e0d6f1e242c1/coverage-7.12.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5b3c889c0b8b283a24d721a9eabc8ccafcfc3aebf167e4cd0d0e23bf8ec4e339", size = 264671, upload-time = "2025-11-18T13:34:02.795Z" }, + { url = "https://files.pythonhosted.org/packages/87/ec/e2822a795c1ed44d569980097be839c5e734d4c0c1119ef8e0a073496a30/coverage-7.12.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8bb5b894b3ec09dcd6d3743229dc7f2c42ef7787dc40596ae04c0edda487371e", size = 259231, upload-time = "2025-11-18T13:34:04.397Z" }, + { url = "https://files.pythonhosted.org/packages/72/c5/a7ec5395bb4a49c9b7ad97e63f0c92f6bf4a9e006b1393555a02dae75f16/coverage-7.12.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:79a44421cd5fba96aa57b5e3b5a4d3274c449d4c622e8f76882d76635501fd13", size = 262137, upload-time = "2025-11-18T13:34:06.068Z" }, + { url = "https://files.pythonhosted.org/packages/67/0c/02c08858b764129f4ecb8e316684272972e60777ae986f3865b10940bdd6/coverage-7.12.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:33baadc0efd5c7294f436a632566ccc1f72c867f82833eb59820ee37dc811c6f", size = 259745, upload-time = "2025-11-18T13:34:08.04Z" }, + { url = "https://files.pythonhosted.org/packages/5a/04/4fd32b7084505f3829a8fe45c1a74a7a728cb251aaadbe3bec04abcef06d/coverage-7.12.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:c406a71f544800ef7e9e0000af706b88465f3573ae8b8de37e5f96c59f689ad1", size = 258570, upload-time = "2025-11-18T13:34:09.676Z" }, + { url = "https://files.pythonhosted.org/packages/48/35/2365e37c90df4f5342c4fa202223744119fe31264ee2924f09f074ea9b6d/coverage-7.12.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e71bba6a40883b00c6d571599b4627f50c360b3d0d02bfc658168936be74027b", size = 260899, upload-time = "2025-11-18T13:34:11.259Z" }, + { url = "https://files.pythonhosted.org/packages/05/56/26ab0464ca733fa325e8e71455c58c1c374ce30f7c04cebb88eabb037b18/coverage-7.12.0-cp314-cp314t-win32.whl", hash = "sha256:9157a5e233c40ce6613dead4c131a006adfda70e557b6856b97aceed01b0e27a", size = 221313, upload-time = "2025-11-18T13:34:12.863Z" }, + { url = "https://files.pythonhosted.org/packages/da/1c/017a3e1113ed34d998b27d2c6dba08a9e7cb97d362f0ec988fcd873dcf81/coverage-7.12.0-cp314-cp314t-win_amd64.whl", hash = "sha256:e84da3a0fd233aeec797b981c51af1cabac74f9bd67be42458365b30d11b5291", size = 222423, upload-time = "2025-11-18T13:34:15.14Z" }, + { url = "https://files.pythonhosted.org/packages/4c/36/bcc504fdd5169301b52568802bb1b9cdde2e27a01d39fbb3b4b508ab7c2c/coverage-7.12.0-cp314-cp314t-win_arm64.whl", hash = "sha256:01d24af36fedda51c2b1aca56e4330a3710f83b02a5ff3743a6b015ffa7c9384", size = 220459, upload-time = "2025-11-18T13:34:17.222Z" }, + { url = "https://files.pythonhosted.org/packages/ce/a3/43b749004e3c09452e39bb56347a008f0a0668aad37324a99b5c8ca91d9e/coverage-7.12.0-py3-none-any.whl", hash = "sha256:159d50c0b12e060b15ed3d39f87ed43d4f7f7ad40b8a534f4dd331adbb51104a", size = 209503, upload-time = "2025-11-18T13:34:18.892Z" }, ] [package.optional-dependencies] @@ -764,6 +764,7 @@ dev = [ { name = "pytest-pretty" }, { name = "pytest-xdist" }, { name = "ruff" }, + { name = "strict-no-cover" }, { name = "trio" }, ] docs = [ @@ -800,7 +801,7 @@ provides-extras = ["cli", "rich", "ws"] [package.metadata.requires-dev] dev = [ - { name = "coverage", extras = ["toml"], specifier = ">=7.13.1" }, + { name = "coverage", extras = ["toml"], specifier = ">=7.10.7,<7.13" }, { name = "dirty-equals", specifier = ">=0.9.0" }, { name = "inline-snapshot", specifier = ">=0.23.0" }, { name = "mcp", extras = ["cli", "ws"], editable = "." }, @@ -812,6 +813,7 @@ dev = [ { name = "pytest-pretty", specifier = ">=1.2.0" }, { name = "pytest-xdist", specifier = ">=3.6.1" }, { name = "ruff", specifier = ">=0.8.5" }, + { name = "strict-no-cover", git = "https://github.com/pydantic/strict-no-cover" }, { name = "trio", specifier = ">=0.26.2" }, ] docs = [ @@ -2505,6 +2507,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/51/da/545b75d420bb23b5d494b0517757b351963e974e79933f01e05c929f20a6/starlette-0.49.1-py3-none-any.whl", hash = "sha256:d92ce9f07e4a3caa3ac13a79523bd18e3bc0042bb8ff2d759a8e7dd0e1859875", size = 74175, upload-time = "2025-10-28T17:34:09.13Z" }, ] +[[package]] +name = "strict-no-cover" +version = "0.1.1" +source = { git = "https://github.com/pydantic/strict-no-cover#7fc59da2c4dff919db2095a0f0e47101b657131d" } +dependencies = [ + { name = "pydantic", version = "2.11.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.14'" }, + { name = "pydantic", version = "2.12.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.14'" }, +] + [[package]] name = "tomli" version = "2.2.1"