From 310404bcebfb0eed2bada3686d594020a395dafa Mon Sep 17 00:00:00 2001 From: "qwen.ai[bot]" Date: Wed, 12 Aug 2026 14:41:16 +0000 Subject: [PATCH] docs: add comprehensive docstrings to orchestration module - Add docstring to utc_now_iso() explaining return format - Add docstring to classify_exception() with args, returns, and examples - Add docstring to run_module() detailing timeout and error handling behavior - Add docstring to gather_modules() describing concurrent execution semantics --- src/netsleuth/orchestration.py | 59 ++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/src/netsleuth/orchestration.py b/src/netsleuth/orchestration.py index cc2a517..ce45533 100644 --- a/src/netsleuth/orchestration.py +++ b/src/netsleuth/orchestration.py @@ -14,10 +14,34 @@ _RETRYABLE_STATUS_FLOOR = 500 def utc_now_iso() -> str: + """Return the current UTC timestamp in ISO 8601 format. + + Returns: + A string like '2025-01-15T14:30:00Z' representing the current UTC time. + """ return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ") def classify_exception(exc: BaseException) -> tuple[str, bool]: + """Classify an exception into a probe error kind and retryability. + + Maps exceptions to one of the standard error kinds defined in models.py + and determines whether the operation should be retried. + + Args: + exc: The exception to classify. + + Returns: + A tuple of (kind, retryable) where kind is one of the ERROR_KINDS + values and retryable indicates whether the operation might succeed + if retried. + + Examples: + >>> classify_exception(asyncio.TimeoutError()) + ('timeout', True) + >>> classify_exception(PermissionError()) + ('no_privilege', False) + """ if isinstance(exc, (asyncio.TimeoutError, TimeoutError, httpx.TimeoutException)): return "timeout", True if isinstance(exc, httpx.HTTPStatusError): @@ -45,6 +69,25 @@ async def run_module( timeout: float, source: str | None = None, ) -> ModuleResult: + """Execute a probe module with timeout and error handling. + + Runs the given coroutine with a timeout, catching any exceptions and + converting them into structured ProbeError objects. Tracks execution + timing and ensures the result has consistent metadata. + + Args: + name: The module name for identification in results. + coro: The coroutine to execute (typically an async probe function). + timeout: Maximum time in seconds to wait for the coroutine. + source: Optional source identifier for errors; defaults to name. + + Returns: + A ModuleResult with status 'ok', 'partial', or 'failed', containing + either the probe data or structured error information. + + Raises: + asyncio.CancelledError: Propagated without modification. + """ started_at = utc_now_iso() began = time.perf_counter() try: @@ -71,6 +114,22 @@ async def run_module( async def gather_modules(*results: Awaitable[ModuleResult]) -> list[ModuleResult]: + """Run multiple modules concurrently and collect their results. + + Executes all provided coroutines in parallel using asyncio.gather, + converting any exceptions into failed ModuleResult objects so that + one failing module doesn't prevent others from completing. + + Args: + *results: Variable number of awaitable ModuleResult coroutines. + + Returns: + A list of ModuleResult objects, one for each input coroutine. + Failed coroutines are represented as ModuleResult with status 'failed'. + + Raises: + asyncio.CancelledError: Propagated without modification. + """ gathered = await asyncio.gather(*results, return_exceptions=True) out: list[ModuleResult] = [] for item in gathered: