Coverage for src / lexigram / contracts / web / http_protocols.py: 100%
51 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-08-19 05:41 +0800
« prev ^ index » next coverage.py v7.13.5, created at 2026-08-19 05:41 +0800
1"""Service mesh protocols.
3Protocols for service discovery, load balancing, and communication.
4"""
6from __future__ import annotations
8from typing import TYPE_CHECKING, Any, Protocol, runtime_checkable
10from lexigram.contracts.web.http_types import ServiceInfo as ServiceInfo
12if TYPE_CHECKING:
13 from lexigram.contracts.web.http_models import HttpResponse
16@runtime_checkable
17class ServiceMeshRegistryProtocol(Protocol):
18 """Protocol for service registry implementations.
20 The service registry manages service instance registration
21 and discovery.
23 Example:
24 ```python
25 class ConsulRegistry:
26 async def register(self, service: ServiceInfo) -> None:
27 await self._client.agent.service.register(
28 name=service.name,
29 address=service.host,
30 port=service.port,
31 )
32 ```
33 """
35 async def register(self, service: ServiceInfo) -> None:
36 """Register a service instance.
38 Args:
39 service: ServiceInfo to register.
40 """
41 ...
43 async def deregister(self, service_name: str, host: str, port: int) -> None:
44 """Deregister a service instance.
46 Args:
47 service_name: Name of the service.
48 host: Service host.
49 port: Service port.
50 """
51 ...
53 async def discover(self, service_name: str) -> list[ServiceInfo]:
54 """Discover all instances of a service.
56 Args:
57 service_name: Name of the service.
59 Returns:
60 List of ServiceInfo instances.
61 """
62 ...
64 async def get_service(
65 self,
66 service_name: str,
67 host: str,
68 port: int,
69 ) -> ServiceInfo | None:
70 """Get a specific service instance.
72 Args:
73 service_name: Name of the service.
74 host: Service host.
75 port: Service port.
77 Returns:
78 ServiceInfo if found, None otherwise.
79 """
80 ...
82 async def list_services(self) -> list[str]:
83 """List all registered service names.
85 Returns:
86 List of service names.
87 """
88 ...
91@runtime_checkable
92class SelectorProtocol(Protocol):
93 """Protocol for load balancing selectors.
95 Selectors choose which service instance to route to.
96 """
98 async def select(self, instances: list[ServiceInfo]) -> ServiceInfo | None:
99 """Select an instance from the available instances.
101 Args:
102 instances: List of available service instances.
104 Returns:
105 Selected instance or None.
106 """
107 ...
110@runtime_checkable
111class HTTPSessionProtocol(Protocol):
112 """Protocol for underlying HTTP session implementations.
114 This allows the HTTPClient to be backend-agnostic, enabling easier
115 testing and support for different HTTP libraries.
116 """
118 async def request(self, method: str, url: str, **kwargs: Any) -> Any:
119 """Perform an HTTP request and return a raw response object."""
120 ...
122 async def close(self) -> None:
123 """Close the session and release resources."""
124 ...
127@runtime_checkable
128class HTTPClientProtocol(Protocol):
129 """Protocol for HTTP client implementations."""
131 async def start(self) -> None:
132 """Start the HTTP client and its underlying connection pool."""
133 ...
135 async def stop(self) -> None:
136 """Stop the HTTP client and release all connection resources."""
137 ...
139 async def request(self, method: str, url: str, **kwargs: Any) -> HttpResponse:
140 """Perform an arbitrary HTTP request.
142 Args:
143 method: HTTP method (GET, POST, PUT, …).
144 url: Request URL.
145 **kwargs: Additional options (headers, data, json, params, …).
147 Returns:
148 Framework-owned :class:`HttpResponse`.
149 """
150 ...
152 async def get(self, url: str, **kwargs: Any) -> HttpResponse:
153 """Perform GET request.
155 Args:
156 url: Request URL.
157 **kwargs: Additional options.
159 Returns:
160 Framework-owned :class:`HttpResponse`.
161 """
162 ...
164 async def post(self, url: str, **kwargs: Any) -> HttpResponse:
165 """Perform POST request.
167 Args:
168 url: Request URL.
169 **kwargs: Additional options (e.g. ``json=``, ``data=``).
171 Returns:
172 Framework-owned :class:`HttpResponse`.
173 """
174 ...
176 async def put(self, url: str, **kwargs: Any) -> HttpResponse:
177 """Perform PUT request.
179 Args:
180 url: Request URL.
181 **kwargs: Additional options.
183 Returns:
184 Framework-owned :class:`HttpResponse`.
185 """
186 ...
188 async def delete(self, url: str, **kwargs: Any) -> HttpResponse:
189 """Perform DELETE request.
191 Args:
192 url: Request URL.
193 **kwargs: Additional options.
195 Returns:
196 Framework-owned :class:`HttpResponse`.
197 """
198 ...
200 async def patch(self, url: str, **kwargs: Any) -> HttpResponse:
201 """Perform PATCH request.
203 Args:
204 url: Request URL.
205 **kwargs: Additional options.
207 Returns:
208 Framework-owned :class:`HttpResponse`.
209 """
210 ...
212 async def head(self, url: str, **kwargs: Any) -> HttpResponse:
213 """Perform HEAD request.
215 Args:
216 url: Request URL.
217 **kwargs: Additional options.
219 Returns:
220 Framework-owned :class:`HttpResponse`.
221 """
222 ...
225@runtime_checkable
226class InterceptorProtocol(Protocol):
227 """Protocol for request/response interceptors.
229 Interceptors are applied to every request/response cycle in
230 :class:`~lexigram.http.HTTPClient`. Implementations receive typed
231 ``RequestContext`` objects and may modify them before the request is
232 dispatched, or inspect / annotate the raw response after it arrives.
234 Example:
235 class LoggingInterceptor:
236 async def intercept_request(self, context: Any) -> Any:
237 logger.info("outbound_request", method=context.method, url=context.url)
238 return context
240 async def intercept_response(self, response: Any) -> Any:
241 logger.info("inbound_response", status=response.status)
242 return response
243 """
245 async def intercept_request(self, context: Any) -> Any:
246 """Called before a request is dispatched.
248 Args:
249 context: :class:`~lexigram.http.RequestContext` for the outbound
250 request. Implementations may mutate and return it.
252 Returns:
253 The (possibly modified) request context.
254 """
255 ...
257 async def intercept_response(self, response: Any) -> Any:
258 """Called after a response is received from the server.
260 Args:
261 response: Raw response object from the underlying HTTP library.
262 Implementations may annotate or replace it.
264 Returns:
265 The (possibly modified) response.
266 """
267 ...
270@runtime_checkable
271class InterceptorChainProtocol(Protocol):
272 """Protocol for interceptor chain management.
274 Manages a collection of interceptors and orchestrates their execution
275 in sequence. Each interceptor in the chain processes the request/response
276 before passing control to the next interceptor.
278 Example:
279 ```python
280 class InterceptorChain:
281 def __init__(self, interceptors: list[InterceptorProtocol]):
282 self._interceptors = interceptors
284 async def execute_request(self, context: Any) -> Any:
285 for interceptor in self._interceptors:
286 context = await interceptor.intercept_request(context)
287 return context
289 async def execute_response(self, response: Any) -> Any:
290 for interceptor in reversed(self._interceptors):
291 response = await interceptor.intercept_response(response)
292 return response
293 ```
294 """
296 def add_interceptor(self, interceptor: InterceptorProtocol) -> None:
297 """Add an interceptor to the chain.
299 Args:
300 interceptor: The interceptor to add.
301 """
302 ...
304 def remove_interceptor(self, interceptor: InterceptorProtocol) -> None:
305 """Remove an interceptor from the chain.
307 Args:
308 interceptor: The interceptor to remove.
309 """
310 ...
312 async def process_request(self, context: Any) -> Any:
313 """Process request through the interceptor chain.
315 Args:
316 context: Request context to process.
318 Returns:
319 Modified request context after all interceptors.
320 """
321 ...
323 async def process_response(self, response: Any) -> Any:
324 """Process response through the interceptor chain.
326 Args:
327 response: Response to process.
329 Returns:
330 Modified response after all interceptors.
331 """
332 ...
335@runtime_checkable
336class ConnectMetricsCollectorProtocol(Protocol):
337 """Protocol for service metrics collection."""
339 def record_request(
340 self,
341 service: str,
342 method: str,
343 duration: float,
344 status: int,
345 ) -> None:
346 """Record a request metric."""
347 ...
349 def get_metrics(self, service: str) -> dict[str, Any]:
350 """Get metrics for a service."""
351 ...
354@runtime_checkable
355class WebSocketProtocol(Protocol):
356 """Framework-agnostic WebSocket connection contract.
358 Abstracts over concrete WebSocket objects (Starlette, AIOHTTP, …) so
359 the GraphQL subscription transport layer stays decoupled from the
360 underlying web framework.
361 """
363 async def accept(
364 self,
365 subprotocol: str | None = None,
366 ) -> None:
367 """Accept the WebSocket upgrade handshake.
369 Args:
370 subprotocol: Optional WebSocket sub-protocol to negotiate
371 (e.g. ``"graphql-transport-ws"``).
372 """
373 ...
375 async def receive_text(self) -> str:
376 """Receive the next text frame from the client.
378 Returns:
379 Raw text payload of the received frame.
380 """
381 ...
383 async def receive_json(self) -> Any:
384 """Receive the next frame and deserialise it as JSON.
386 Returns:
387 Deserialised JSON value.
388 """
389 ...
391 async def send_text(self, data: str) -> None:
392 """Send a text frame to the client.
394 Args:
395 data: Text payload to send.
396 """
397 ...
399 async def send_json(self, data: Any) -> None:
400 """Serialise *data* as JSON and send it to the client.
402 Args:
403 data: Value to serialise and send.
404 """
405 ...
407 async def close(self, code: int = 1000, reason: str = "") -> None:
408 """Close the WebSocket connection.
410 Args:
411 code: WebSocket close status code (default 1000 = normal closure).
412 reason: Human-readable close reason.
413 """
414 ...
417__all__ = [
418 "ConnectMetricsCollectorProtocol",
419 "HTTPClientProtocol",
420 "HTTPSessionProtocol",
421 "InterceptorChainProtocol",
422 "InterceptorProtocol",
423 "SelectorProtocol",
424 "ServiceInfo",
425 "ServiceMeshRegistryProtocol",
426 "WebSocketProtocol",
427]