09e9f3545f
CodeQL / Analyze (push) Waiting to run
dependency-audit / pip-audit (push) Waiting to run
Test / Code Quality (push) Has been cancelled
Test / Test (macos-latest, Python 3.10) (push) Has been cancelled
Test / Test (macos-latest, Python 3.11) (push) Has been cancelled
Test / Test (macos-latest, Python 3.12) (push) Has been cancelled
Test / Test (macos-latest, Python 3.13) (push) Has been cancelled
Test / Test (macos-latest, Python 3.14) (push) Has been cancelled
Test / Test (ubuntu-latest, Python 3.10) (push) Has been cancelled
Test / Test (ubuntu-latest, Python 3.11) (push) Has been cancelled
Test / Test (ubuntu-latest, Python 3.12) (push) Has been cancelled
Test / Test (ubuntu-latest, Python 3.13) (push) Has been cancelled
Test / Test (ubuntu-latest, Python 3.14) (push) Has been cancelled
Test / Test (windows-latest, Python 3.10) (push) Has been cancelled
Test / Test (windows-latest, Python 3.11) (push) Has been cancelled
Test / Test (windows-latest, Python 3.12) (push) Has been cancelled
Test / Test (windows-latest, Python 3.13) (push) Has been cancelled
Test / Test (windows-latest, Python 3.14) (push) Has been cancelled
168 lines
5.5 KiB
Python
168 lines
5.5 KiB
Python
"""Typing checks for the capability-Protocol contracts in
|
|
``notebooklm._runtime.contracts``.
|
|
|
|
Phase 7 (docs/refactor-history.md §Migration Plan step 10) replaced the broad
|
|
``Session`` Protocol with shared capability Protocols. The surviving
|
|
shared Protocols are ``RpcCaller`` (~17 consumers), ``LoopGuard`` (2
|
|
consumers), and the pure-transport ``Kernel``. The single-consumer
|
|
``AuthMetadata`` / ``OperationScopeProvider`` Protocols and the unused
|
|
``AsyncWorkRuntime`` composite were inlined into their owning feature
|
|
modules / deleted in issue #1327 — ``AuthMetadata`` now lives in
|
|
``_source.upload`` (used by ``SourceUploadPipeline``) and
|
|
``OperationScopeProvider`` in ``_artifact.polling`` (used by
|
|
``ArtifactPollingService``); mypy enforces their structural conformance
|
|
at the consuming call sites. The standalone
|
|
``DrainHookRegistration`` Protocol previously kept here was deleted in
|
|
Phase 7; drain-hook registration now lives on
|
|
``TransportDrainTracker.register_drain_hook(...)`` in ``_transport_drain.py``.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import inspect
|
|
from collections.abc import Mapping
|
|
from typing import Any
|
|
|
|
import httpx
|
|
|
|
from notebooklm._runtime.contracts import (
|
|
Kernel,
|
|
LoopGuard,
|
|
RpcCaller,
|
|
)
|
|
from notebooklm.rpc.types import RPCMethod
|
|
|
|
|
|
class _RpcCallerImpl:
|
|
async def rpc_call(
|
|
self,
|
|
method: RPCMethod,
|
|
params: list[Any],
|
|
source_path: str = "/",
|
|
allow_null: bool = False,
|
|
_is_retry: bool = False,
|
|
*,
|
|
disable_internal_retries: bool = False,
|
|
operation_variant: str | None = None,
|
|
) -> Any:
|
|
return None
|
|
|
|
|
|
class _LoopGuardImpl:
|
|
def assert_bound_loop(self) -> None:
|
|
return None
|
|
|
|
|
|
class _KernelImpl:
|
|
async def post(
|
|
self,
|
|
url: str,
|
|
headers: Mapping[str, str],
|
|
body: bytes,
|
|
*,
|
|
read_timeout: float | None = None,
|
|
max_response_bytes: int | None = None,
|
|
) -> httpx.Response:
|
|
return httpx.Response(200, content=body)
|
|
|
|
@property
|
|
def cookies(self) -> httpx.Cookies:
|
|
return httpx.Cookies()
|
|
|
|
async def aclose(self) -> None:
|
|
return None
|
|
|
|
|
|
def _public_contract_members(protocol: type[Any]) -> set[str]:
|
|
return {name for name in protocol.__dict__ if not name.startswith("_")}
|
|
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Membership pins — one test per Protocol
|
|
# ----------------------------------------------------------------------
|
|
|
|
|
|
def test_kernel_protocol_has_exactly_three_members() -> None:
|
|
assert _public_contract_members(Kernel) == {"post", "cookies", "aclose"}
|
|
|
|
|
|
def test_rpc_caller_protocol_has_exactly_one_member() -> None:
|
|
assert _public_contract_members(RpcCaller) == {"rpc_call"}
|
|
|
|
|
|
def test_loop_guard_protocol_has_exactly_one_member() -> None:
|
|
assert _public_contract_members(LoopGuard) == {"assert_bound_loop"}
|
|
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Signature pins — load-bearing for feature retypes
|
|
# ----------------------------------------------------------------------
|
|
|
|
|
|
def test_rpc_caller_signature_matches_legacy_session_rpc_call() -> None:
|
|
sig = inspect.signature(RpcCaller.rpc_call)
|
|
assert list(sig.parameters) == [
|
|
"self",
|
|
"method",
|
|
"params",
|
|
"source_path",
|
|
"allow_null",
|
|
"_is_retry",
|
|
"disable_internal_retries",
|
|
"operation_variant",
|
|
]
|
|
assert sig.parameters["source_path"].default == "/"
|
|
assert sig.parameters["allow_null"].default is False
|
|
assert sig.parameters["_is_retry"].default is False
|
|
assert sig.parameters["disable_internal_retries"].kind is inspect.Parameter.KEYWORD_ONLY
|
|
assert sig.parameters["disable_internal_retries"].default is False
|
|
assert sig.parameters["operation_variant"].kind is inspect.Parameter.KEYWORD_ONLY
|
|
assert sig.parameters["operation_variant"].default is None
|
|
|
|
|
|
def test_kernel_protocol_signatures_are_pinned() -> None:
|
|
post = inspect.signature(Kernel.post)
|
|
assert list(post.parameters) == [
|
|
"self",
|
|
"url",
|
|
"headers",
|
|
"body",
|
|
"read_timeout",
|
|
"max_response_bytes",
|
|
]
|
|
assert post.parameters["headers"].annotation == "Mapping[str, str]"
|
|
assert post.parameters["body"].annotation == "bytes"
|
|
assert post.parameters["read_timeout"].kind is inspect.Parameter.KEYWORD_ONLY
|
|
assert post.parameters["read_timeout"].default is None
|
|
assert post.parameters["max_response_bytes"].kind is inspect.Parameter.KEYWORD_ONLY
|
|
assert post.parameters["max_response_bytes"].default is None
|
|
assert post.return_annotation == "httpx.Response"
|
|
|
|
cookies = inspect.signature(Kernel.cookies.fget)
|
|
assert cookies.return_annotation == "httpx.Cookies"
|
|
|
|
aclose = inspect.signature(Kernel.aclose)
|
|
assert list(aclose.parameters) == ["self"]
|
|
assert aclose.return_annotation == "None"
|
|
|
|
|
|
def test_loop_guard_signature_is_pinned() -> None:
|
|
sig = inspect.signature(LoopGuard.assert_bound_loop)
|
|
assert list(sig.parameters) == ["self"]
|
|
assert sig.return_annotation == "None"
|
|
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Structural conformance — mypy verifies these assignments
|
|
# ----------------------------------------------------------------------
|
|
|
|
|
|
def test_structural_implementations_satisfy_protocols() -> None:
|
|
kernel: Kernel = _KernelImpl()
|
|
rpc: RpcCaller = _RpcCallerImpl()
|
|
loop_guard: LoopGuard = _LoopGuardImpl()
|
|
|
|
assert kernel is not None
|
|
assert rpc is not None
|
|
assert loop_guard is not None
|