Narrow message_handler's parameter to notifications and exceptions (#3168)
This commit is contained in:
parent
47bfa85e83
commit
814072c94d
28 changed files with 74 additions and 147 deletions
|
|
@ -135,7 +135,7 @@ Two more. Neither declares anything.
|
|||
|
||||
`logging_callback` receives every `notifications/message` a server sends, as `LoggingMessageNotificationParams` (`level`, `logger`, `data`). Protocol logging is itself deprecated by the 2026-07-28 spec (**[Logging](../handlers/logging.md)** has what to do instead), so this callback exists for the servers that still emit it.
|
||||
|
||||
`message_handler` is the catch-all: every server notification reaches it (as well as its specific callback), and on a stream-backed transport so does every transport-level `Exception`. The one pattern worth knowing is `if isinstance(message, Exception): raise message`, so a broken connection fails loudly instead of vanishing.
|
||||
`message_handler` is the catch-all: every server notification the session surfaces reaches it (as well as its specific callback), and on a stream-backed transport so does every transport-level `Exception`. Two never do: `notifications/cancelled` is applied by the SDK rather than surfaced, and a subscription acknowledgment for a live `listen()` stream is consumed by that stream. Annotate the parameter with `IncomingMessage` (`ServerNotification | Exception`, exported from `mcp.client`). The one pattern worth knowing is `if isinstance(message, Exception): raise message`, so a broken connection fails loudly instead of vanishing.
|
||||
|
||||
## Recap
|
||||
|
||||
|
|
|
|||
|
|
@ -1688,7 +1688,9 @@ Behavior changes:
|
|||
- **`send_notification` no longer takes `related_request_id`, and `send_request` no longer accepts `ServerMessageMetadata`.** No client transport ever serialized these hints; progress and response correlation via `progressToken` and the request id is unaffected.
|
||||
- **Client callbacks now receive `mcp.client.ClientRequestContext`** (its `request_id` is always populated); the `mcp.shared.context.RequestContext` generic is deleted. Annotations spelled `RequestContext[ClientSession, Any]` become `ClientRequestContext` (details in [`RequestContext` type parameters simplified](#requestcontext-type-parameters-simplified)).
|
||||
|
||||
`mcp.shared.session` is now a compatibility module: `ProgressFnT` is re-exported (its home is `mcp.shared.dispatcher`), and `RequestResponder` remains as a typing-only stub so `MessageHandlerFnT` annotations keep importing. `RequestResponder.respond()` no longer exists, and neither do the cancellation-tracking members (`cancel()`, the `cancelled` and `in_flight` properties, the `on_complete` constructor argument) or `BaseSession._in_flight`; inbound cancellation is handled by `JSONRPCDispatcher`.
|
||||
- **`message_handler` no longer receives requests.** Server-initiated requests are answered by the typed callbacks (`sampling_callback`, `elicitation_callback`, `list_roots_callback`), so the handler's parameter is now `IncomingMessage = ServerNotification | Exception`, exported from `mcp.client`. Replace the hand-written v1 union `RequestResponder[ServerRequest, ClientResult] | ServerNotification | Exception` with `IncomingMessage`; `RequestResponder` is gone (below), so the old annotation no longer imports.
|
||||
|
||||
The `mcp.shared.session` module is gone. `RequestResponder` is removed — `respond()`, the cancellation-tracking members (`cancel()`, the `cancelled` and `in_flight` properties, the `on_complete` constructor argument) and `BaseSession._in_flight` have no replacement; inbound cancellation is handled by `JSONRPCDispatcher`. `ProgressFnT` now lives only in `mcp.shared.dispatcher`, and `RequestId` in `mcp_types`.
|
||||
|
||||
### Experimental Tasks support removed
|
||||
|
||||
|
|
|
|||
|
|
@ -146,7 +146,7 @@ Each of these is a section in the **[Migration Guide](migration.md)**:
|
|||
|
||||
* The **WebSocket transport**, both sides, and the `mcp[ws]` extra. It was never part of the MCP specification.
|
||||
* The **experimental Tasks** API (`mcp.*.experimental`). 2026-07-28 moves tasks out of the core protocol and into an official extension ([SEP-2663](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2663)), which this SDK does not implement yet.
|
||||
* `mcp.types`, `mcp.shared.version`, and `mcp.shared.progress` as import paths.
|
||||
* `mcp.types`, `mcp.shared.version`, `mcp.shared.progress`, and `mcp.shared.session` (with the `RequestResponder` stub v1 `message_handler` annotations imported) as import paths.
|
||||
* The deprecated `streamablehttp_client` spelling, and the `get_session_id` callback from `streamable_http_client` (which now yields exactly two streams).
|
||||
* `McpError`, renamed **`MCPError`** with a direct `(code, message, data)` constructor.
|
||||
* `MCPServer.get_context()`, `mount_path=`, and the lowlevel `Server`'s decorator methods, ContextVar, and handler dicts.
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
import anyio
|
||||
import mcp_types as types
|
||||
|
||||
from mcp.client import Client
|
||||
from mcp.client import Client, IncomingMessage
|
||||
from stories._harness import Target, run_client
|
||||
|
||||
|
||||
|
|
@ -13,7 +13,7 @@ async def main(target: Target, *, mode: str = "auto") -> None:
|
|||
received: list[types.ResourceListChangedNotification] = []
|
||||
seen = anyio.Event()
|
||||
|
||||
async def on_message(message: object) -> None:
|
||||
async def on_message(message: IncomingMessage) -> None:
|
||||
if isinstance(message, types.ResourceListChangedNotification):
|
||||
received.append(message)
|
||||
seen.set()
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import anyio
|
|||
import mcp_types as types
|
||||
from mcp_types.version import HANDSHAKE_PROTOCOL_VERSIONS
|
||||
|
||||
from mcp.client import Client, ClientRequestContext
|
||||
from mcp.client import Client, ClientRequestContext, IncomingMessage
|
||||
from stories._harness import Target, run_client
|
||||
|
||||
|
||||
|
|
@ -18,7 +18,7 @@ async def main(target: Target, *, mode: str = "auto") -> None:
|
|||
return types.ElicitResult(action="cancel")
|
||||
return types.ElicitResult(action="accept", content={"confirm": answer == "confirm"})
|
||||
|
||||
async def on_message(message: object) -> None:
|
||||
async def on_message(message: IncomingMessage) -> None:
|
||||
if isinstance(message, types.ResourceListChangedNotification):
|
||||
list_changed.set()
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ from mcp.client.extension import (
|
|||
UnexpectedClaimedResult,
|
||||
advertise,
|
||||
)
|
||||
from mcp.client.session import ClientSession
|
||||
from mcp.client.session import ClientSession, IncomingMessage
|
||||
|
||||
__all__ = [
|
||||
"CacheConfig",
|
||||
|
|
@ -32,6 +32,7 @@ __all__ = [
|
|||
"ClientExtension",
|
||||
"ClientRequestContext",
|
||||
"ClientSession",
|
||||
"IncomingMessage",
|
||||
"InMemoryResponseCacheStore",
|
||||
"InputRequiredRoundsExceededError",
|
||||
"NotificationBinding",
|
||||
|
|
|
|||
|
|
@ -9,11 +9,10 @@ import anyio
|
|||
import mcp_types as types
|
||||
|
||||
from mcp.client._transport import ReadStream, WriteStream
|
||||
from mcp.client.session import ClientSession
|
||||
from mcp.client.session import ClientSession, IncomingMessage
|
||||
from mcp.client.sse import sse_client
|
||||
from mcp.client.stdio import StdioServerParameters, stdio_client
|
||||
from mcp.shared.message import SessionMessage
|
||||
from mcp.shared.session import RequestResponder
|
||||
|
||||
if not sys.warnoptions:
|
||||
warnings.simplefilter("ignore")
|
||||
|
|
@ -22,9 +21,7 @@ logging.basicConfig(level=logging.INFO)
|
|||
logger = logging.getLogger("client")
|
||||
|
||||
|
||||
async def message_handler(
|
||||
message: RequestResponder[types.ServerRequest, types.ClientResult] | types.ServerNotification | Exception,
|
||||
) -> None:
|
||||
async def message_handler(message: IncomingMessage) -> None:
|
||||
if isinstance(message, Exception):
|
||||
logger.error("Error: %s", message)
|
||||
return
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ from mcp.client.session import (
|
|||
ClientRequestContext,
|
||||
ClientSession,
|
||||
ElicitationFnT,
|
||||
IncomingMessage,
|
||||
ListRootsFnT,
|
||||
LoggingFnT,
|
||||
MessageHandlerFnT,
|
||||
|
|
@ -68,7 +69,6 @@ from mcp.shared.dispatcher import Dispatcher, ProgressFnT
|
|||
from mcp.shared.exceptions import MCPDeprecationWarning, MCPError
|
||||
from mcp.shared.extension import validate_extension_identifier
|
||||
from mcp.shared.jsonrpc_dispatcher import JSONRPCDispatcher
|
||||
from mcp.shared.session import RequestResponder
|
||||
from mcp.shared.subscriptions import event_to_notification
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
|
@ -155,9 +155,7 @@ def _strip_userinfo(url: str) -> str:
|
|||
def _evicting_message_handler(cache: ClientResponseCache, user_handler: MessageHandlerFnT | None) -> MessageHandlerFnT:
|
||||
"""Wrap the session message handler with cache eviction on server notifications."""
|
||||
|
||||
async def handler(
|
||||
message: RequestResponder[types.ServerRequest, types.ClientResult] | types.ServerNotification | Exception,
|
||||
) -> None:
|
||||
async def handler(message: IncomingMessage) -> None:
|
||||
if isinstance(message, types.ServerNotification):
|
||||
try:
|
||||
await cache.evict_for_notification(message)
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ from dataclasses import dataclass
|
|||
from functools import reduce
|
||||
from operator import or_
|
||||
from types import TracebackType
|
||||
from typing import Annotated, Any, Final, Literal, Protocol, cast, overload
|
||||
from typing import Annotated, Any, Final, Literal, Protocol, TypeAlias, cast, overload
|
||||
|
||||
import anyio
|
||||
import anyio.abc
|
||||
|
|
@ -53,7 +53,6 @@ from mcp.shared.inbound import (
|
|||
)
|
||||
from mcp.shared.jsonrpc_dispatcher import JSONRPCDispatcher, cancelled_request_id_from_params
|
||||
from mcp.shared.message import ClientMessageMetadata, SessionMessage
|
||||
from mcp.shared.session import RequestResponder
|
||||
from mcp.shared.subscriptions import SUBSCRIPTION_ID_META_KEY, event_from_wire
|
||||
from mcp.shared.transport_context import TransportContext
|
||||
|
||||
|
|
@ -172,16 +171,20 @@ class LoggingFnT(Protocol):
|
|||
async def __call__(self, params: types.LoggingMessageNotificationParams) -> None: ... # pragma: no branch
|
||||
|
||||
|
||||
IncomingMessage: TypeAlias = types.ServerNotification | Exception
|
||||
"""What `message_handler` receives: the server notifications the session surfaces, plus transport-level exceptions.
|
||||
|
||||
`notifications/cancelled` is applied by the dispatcher and never surfaced, and a
|
||||
`notifications/subscriptions/acknowledged` for a live `listen()` stream is consumed by that
|
||||
stream, so neither reaches the handler.
|
||||
"""
|
||||
|
||||
|
||||
class MessageHandlerFnT(Protocol):
|
||||
async def __call__(
|
||||
self,
|
||||
message: RequestResponder[types.ServerRequest, types.ClientResult] | types.ServerNotification | Exception,
|
||||
) -> None: ... # pragma: no branch
|
||||
async def __call__(self, message: IncomingMessage) -> None: ... # pragma: no branch
|
||||
|
||||
|
||||
async def _default_message_handler(
|
||||
message: RequestResponder[types.ServerRequest, types.ClientResult] | types.ServerNotification | Exception,
|
||||
) -> None:
|
||||
async def _default_message_handler(message: IncomingMessage) -> None:
|
||||
await anyio.lowlevel.checkpoint()
|
||||
|
||||
|
||||
|
|
@ -331,8 +334,10 @@ class ClientSession:
|
|||
`dispatcher=`), enter as an async context manager, then call
|
||||
`initialize()`. The dispatcher owns the receive loop and request
|
||||
correlation; this class owns the typed MCP layer and the constructor
|
||||
callbacks. Transport `Exception` items reach `message_handler` only when
|
||||
the session builds its own dispatcher from a stream pair.
|
||||
callbacks. Transport `Exception` items reach `message_handler` on any
|
||||
stream-backed dispatcher (`JSONRPCDispatcher`), whether built here from a
|
||||
stream pair or supplied without a stream-exception hook of its own; an
|
||||
in-process `DirectDispatcher` carries none.
|
||||
|
||||
Extension `result_claims` fold into tools/call parsing at `adopt()`;
|
||||
`notification_bindings` observe vendor notifications via bounded FIFOs.
|
||||
|
|
|
|||
|
|
@ -25,8 +25,8 @@ from mcp.client.sse import sse_client
|
|||
from mcp.client.stdio import StdioServerParameters
|
||||
from mcp.client.streamable_http import streamable_http_client
|
||||
from mcp.shared._httpx_utils import create_mcp_http_client
|
||||
from mcp.shared.dispatcher import ProgressFnT
|
||||
from mcp.shared.exceptions import MCPError
|
||||
from mcp.shared.session import ProgressFnT
|
||||
|
||||
|
||||
class SseServerParameters(BaseModel):
|
||||
|
|
|
|||
|
|
@ -1,22 +0,0 @@
|
|||
"""Compatibility names that outlived the removed v1 session layer (`BaseSession`)."""
|
||||
|
||||
from typing import Generic, TypeVar
|
||||
|
||||
from mcp_types import RequestParamsMeta
|
||||
|
||||
from mcp.shared.dispatcher import ProgressFnT as ProgressFnT
|
||||
from mcp.shared.message import MessageMetadata
|
||||
|
||||
RequestId = str | int
|
||||
|
||||
ReceiveRequestT = TypeVar("ReceiveRequestT")
|
||||
SendResultT = TypeVar("SendResultT")
|
||||
|
||||
|
||||
class RequestResponder(Generic[ReceiveRequestT, SendResultT]):
|
||||
"""Typing stub for the v1 responder; the SDK never instantiates it."""
|
||||
|
||||
request_id: RequestId
|
||||
request_meta: RequestParamsMeta | None
|
||||
request: ReceiveRequestT
|
||||
message_metadata: MessageMetadata
|
||||
|
|
@ -42,7 +42,7 @@ from mcp_types import (
|
|||
)
|
||||
from mcp_types.version import LATEST_MODERN_VERSION
|
||||
|
||||
from mcp.client import Client
|
||||
from mcp.client import Client, IncomingMessage
|
||||
from mcp.client._transport import TransportStreams
|
||||
from mcp.client.caching import (
|
||||
CacheConfig,
|
||||
|
|
@ -57,13 +57,10 @@ from mcp.server.caching import CacheHint
|
|||
from mcp.shared.exceptions import MCPError
|
||||
from mcp.shared.memory import MessageStream, create_client_server_memory_streams
|
||||
from mcp.shared.message import SessionMessage
|
||||
from mcp.shared.session import RequestResponder
|
||||
from tests.interaction._connect import BASE_URL, mounted_app
|
||||
|
||||
pytestmark = pytest.mark.anyio
|
||||
|
||||
IncomingMessage = RequestResponder[types.ServerRequest, types.ClientResult] | types.ServerNotification | Exception
|
||||
|
||||
|
||||
def _coordinator(client: Client) -> ClientResponseCache:
|
||||
cache = client._response_cache
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
from typing import Literal
|
||||
|
||||
import mcp_types as types
|
||||
import pytest
|
||||
from mcp_types import (
|
||||
LoggingMessageNotificationParams,
|
||||
|
|
@ -8,8 +7,8 @@ from mcp_types import (
|
|||
)
|
||||
|
||||
from mcp import Client
|
||||
from mcp.client import IncomingMessage
|
||||
from mcp.server.mcpserver import Context, MCPServer
|
||||
from mcp.shared.session import RequestResponder
|
||||
|
||||
|
||||
class LoggingCollector:
|
||||
|
|
@ -55,9 +54,7 @@ async def test_logging_callback():
|
|||
return True
|
||||
|
||||
# Create a message handler to catch exceptions
|
||||
async def message_handler(
|
||||
message: RequestResponder[types.ServerRequest, types.ClientResult] | types.ServerNotification | Exception,
|
||||
) -> None:
|
||||
async def message_handler(message: IncomingMessage) -> None:
|
||||
if isinstance(message, Exception): # pragma: no cover
|
||||
raise message
|
||||
|
||||
|
|
|
|||
|
|
@ -16,8 +16,8 @@ from starlette.responses import JSONResponse, Response
|
|||
from starlette.routing import Route
|
||||
|
||||
from mcp import ClientSession, MCPError
|
||||
from mcp.client import IncomingMessage
|
||||
from mcp.client.streamable_http import streamable_http_client
|
||||
from mcp.shared.session import RequestResponder
|
||||
|
||||
pytestmark = pytest.mark.anyio
|
||||
|
||||
|
|
@ -82,9 +82,7 @@ async def test_non_compliant_notification_response() -> None:
|
|||
"""
|
||||
returned_exception = None
|
||||
|
||||
async def message_handler( # pragma: no cover
|
||||
message: RequestResponder[types.ServerRequest, types.ClientResult] | types.ServerNotification | Exception,
|
||||
) -> None:
|
||||
async def message_handler(message: IncomingMessage) -> None: # pragma: no cover
|
||||
nonlocal returned_exception
|
||||
if isinstance(message, Exception):
|
||||
returned_exception = message
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ from mcp_types.version import HANDSHAKE_PROTOCOL_VERSIONS, LATEST_HANDSHAKE_VERS
|
|||
from pydantic import FileUrl, ValidationError
|
||||
|
||||
from mcp import MCPError
|
||||
from mcp.client import ClientRequestContext
|
||||
from mcp.client import ClientRequestContext, IncomingMessage
|
||||
from mcp.client.client import Client
|
||||
from mcp.client.session import DEFAULT_CLIENT_INFO, ClientSession
|
||||
from mcp.client.subscriptions import ToolsListChanged, listen
|
||||
|
|
@ -45,7 +45,6 @@ from mcp.server import Server, ServerRequestContext
|
|||
from mcp.shared.direct_dispatcher import create_direct_dispatcher_pair
|
||||
from mcp.shared.dispatcher import CallOptions, DispatchContext, OnNotify, OnNotifyIntercept, OnRequest
|
||||
from mcp.shared.message import SessionMessage
|
||||
from mcp.shared.session import RequestResponder
|
||||
from mcp.shared.subscriptions import SUBSCRIPTION_ID_META_KEY
|
||||
from mcp.shared.transport_context import TransportContext
|
||||
|
||||
|
|
@ -123,9 +122,7 @@ async def test_client_session_initialize():
|
|||
)
|
||||
|
||||
# Create a message handler to catch exceptions
|
||||
async def message_handler( # pragma: no cover
|
||||
message: RequestResponder[types.ServerRequest, types.ClientResult] | types.ServerNotification | Exception,
|
||||
) -> None:
|
||||
async def message_handler(message: IncomingMessage) -> None: # pragma: no cover
|
||||
if isinstance(message, Exception):
|
||||
raise message
|
||||
|
||||
|
|
@ -1228,10 +1225,8 @@ async def test_raising_notification_callbacks_over_direct_dispatch_cost_only_tha
|
|||
async def logging_callback(params: types.LoggingMessageNotificationParams) -> None:
|
||||
raise ValueError("logging callback boom")
|
||||
|
||||
async def message_handler(
|
||||
message: RequestResponder[types.ServerRequest, types.ClientResult] | types.ServerNotification | Exception,
|
||||
) -> None:
|
||||
assert not isinstance(message, RequestResponder | Exception)
|
||||
async def message_handler(message: IncomingMessage) -> None:
|
||||
assert not isinstance(message, Exception)
|
||||
teed.append(message)
|
||||
raise ValueError("message handler boom")
|
||||
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ flows — with a single subprocess test for stdio.
|
|||
```text
|
||||
tests/interaction/
|
||||
_requirements.py the requirements manifest (see below)
|
||||
_helpers.py shared type aliases + the wire-recording transport
|
||||
_helpers.py the wire-recording transport
|
||||
_connect.py the transport-parametrized connection factories
|
||||
conftest.py the connect fixture (the transport matrix)
|
||||
test_coverage.py enforces the manifest ↔ test contract
|
||||
|
|
|
|||
|
|
@ -1,28 +1,16 @@
|
|||
"""Shared helpers for the interaction suite.
|
||||
|
||||
Keep this module small: it exists only for (a) types that every test would otherwise have to
|
||||
assemble from the SDK's internals to annotate a client callback, and (b) the recording transport
|
||||
used by the wire-level tests. Server fixtures and assertion helpers belong in the test that uses
|
||||
them.
|
||||
Keep this module small: it exists only for the recording transport used by the wire-level
|
||||
tests. Server fixtures and assertion helpers belong in the test that uses them.
|
||||
"""
|
||||
|
||||
from types import TracebackType
|
||||
|
||||
import anyio
|
||||
from mcp_types import ClientResult, ServerNotification, ServerRequest
|
||||
from typing_extensions import Self
|
||||
|
||||
from mcp.client._transport import ReadStream, Transport, TransportStreams, WriteStream
|
||||
from mcp.shared.message import SessionMessage
|
||||
from mcp.shared.session import RequestResponder
|
||||
|
||||
# TODO: this union is the parameter type of every client message handler (MessageHandlerFnT),
|
||||
# but the SDK does not export a name for it -- writing a correctly-typed handler requires
|
||||
# importing RequestResponder from mcp.shared.session and assembling the union by hand. It
|
||||
# should be a named, exported alias next to MessageHandlerFnT (like ClientRequestContext is
|
||||
# for the request callbacks), at which point this alias can be deleted.
|
||||
IncomingMessage = RequestResponder[ServerRequest, ClientResult] | ServerNotification | Exception
|
||||
"""Everything a client message handler can receive."""
|
||||
|
||||
|
||||
class _RecordingReadStream:
|
||||
|
|
|
|||
|
|
@ -29,13 +29,12 @@ from mcp_types import (
|
|||
)
|
||||
|
||||
from mcp import MCPError
|
||||
from mcp.client import ClientRequestContext, ClientSession
|
||||
from mcp.client import ClientRequestContext, ClientSession, IncomingMessage
|
||||
from mcp.server import Server, ServerRequestContext
|
||||
from mcp.shared.memory import MessageStream, create_client_server_memory_streams
|
||||
from mcp.shared.message import SessionMessage
|
||||
from tests._stamp import Unstamp
|
||||
from tests.interaction._connect import Connect
|
||||
from tests.interaction._helpers import IncomingMessage
|
||||
from tests.interaction._requirements import requirement
|
||||
|
||||
pytestmark = pytest.mark.anyio
|
||||
|
|
|
|||
|
|
@ -28,12 +28,11 @@ from mcp_types import (
|
|||
)
|
||||
|
||||
from mcp import MCPError, UrlElicitationRequiredError
|
||||
from mcp.client import ClientRequestContext, ClientSession
|
||||
from mcp.client import ClientRequestContext, ClientSession, IncomingMessage
|
||||
from mcp.server import Server, ServerRequestContext
|
||||
from mcp.shared.memory import MessageStream, create_client_server_memory_streams
|
||||
from mcp.shared.message import SessionMessage
|
||||
from tests.interaction._connect import Connect
|
||||
from tests.interaction._helpers import IncomingMessage
|
||||
from tests.interaction._requirements import requirement
|
||||
|
||||
pytestmark = pytest.mark.anyio
|
||||
|
|
|
|||
|
|
@ -29,12 +29,11 @@ from mcp_types import (
|
|||
)
|
||||
|
||||
from mcp import MCPError, UrlElicitationRequiredError
|
||||
from mcp.client import ClientRequestContext
|
||||
from mcp.client import ClientRequestContext, IncomingMessage
|
||||
from mcp.server import Server, ServerRequestContext
|
||||
from mcp.server.session import ServerSession
|
||||
from tests._stamp import Unstamp
|
||||
from tests.interaction._connect import Connect
|
||||
from tests.interaction._helpers import IncomingMessage
|
||||
from tests.interaction._requirements import requirement
|
||||
|
||||
pytestmark = pytest.mark.anyio
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue