Coverage for nexusLIMS/exporters/strategies.py: 100%
54 statements
« prev ^ index » next coverage.py v7.11.3, created at 2026-03-24 05:23 +0000
« prev ^ index » next coverage.py v7.11.3, created at 2026-03-24 05:23 +0000
1"""Export strategies for multi-destination export.
3This module implements different strategies for exporting to multiple
4destinations with different success criteria:
5- all: All destinations must succeed
6- first_success: Stop after first success
7- best_effort: Try all, succeed if any succeed
8"""
10from __future__ import annotations
12import logging
13from typing import TYPE_CHECKING
15if TYPE_CHECKING:
16 from nexusLIMS.exporters.base import ExportContext, ExportDestination, ExportResult
17 from nexusLIMS.exporters.registry import ExportStrategy
19_logger = logging.getLogger(__name__)
22def execute_strategy(
23 strategy: ExportStrategy,
24 destinations: list[ExportDestination],
25 context: ExportContext,
26) -> list[ExportResult]:
27 """Execute export strategy.
29 Exports to multiple destinations according to the specified strategy,
30 accumulating results in context.previous_results to enable
31 inter-destination dependencies.
33 Parameters
34 ----------
35 strategy
36 Export strategy to use
37 destinations
38 List of destinations (should be sorted by priority)
39 context
40 Export context (will be mutated to add previous_results)
42 Returns
43 -------
44 list[ExportResult]
45 Results from each destination that was attempted
46 """
47 if strategy == "all":
48 return _strategy_all(destinations, context)
49 if strategy == "first_success":
50 return _strategy_first_success(destinations, context)
51 if strategy == "best_effort":
52 return _strategy_best_effort(destinations, context)
53 msg = f"Unknown export strategy: {strategy}"
54 raise ValueError(msg)
57def _strategy_all(
58 destinations: list[ExportDestination],
59 context: ExportContext,
60) -> list[ExportResult]:
61 """All destinations must succeed.
63 Export to all destinations. If any export fails, log a warning but
64 continue to remaining destinations. The strategy is considered
65 successful only if ALL destinations succeed.
67 Parameters
68 ----------
69 destinations
70 List of destinations to export to
71 context
72 Export context
74 Returns
75 -------
76 list[ExportResult]
77 Results from all destinations
78 """
79 results = []
81 for dest in destinations:
82 _logger.info("Exporting to %s (priority=%d)...", dest.name, dest.priority)
83 result = dest.export(context)
84 results.append(result)
86 # Add result to context for subsequent destinations
87 context.add_result(dest.name, result)
89 if not result.success:
90 _logger.warning(
91 "Export to %s failed (all strategy): %s",
92 dest.name,
93 result.error_message,
94 )
95 else:
96 _logger.info("Export to %s succeeded", dest.name)
98 # Check overall success
99 success_count = sum(1 for r in results if r.success)
100 if success_count == len(results):
101 _logger.info("All %d destination(s) succeeded (all strategy)", len(results))
102 else:
103 _logger.warning(
104 "Only %d/%d destination(s) succeeded (all strategy)",
105 success_count,
106 len(results),
107 )
109 return results
112def _strategy_first_success(
113 destinations: list[ExportDestination],
114 context: ExportContext,
115) -> list[ExportResult]:
116 """Stop after first success.
118 Export to destinations in priority order until one succeeds.
119 Stop immediately after the first successful export.
121 Parameters
122 ----------
123 destinations
124 List of destinations to export to (should be priority-sorted)
125 context
126 Export context
128 Returns
129 -------
130 list[ExportResult]
131 Results from destinations that were attempted (up to first success)
132 """
133 results = []
135 for dest in destinations:
136 _logger.info("Exporting to %s (priority=%d)...", dest.name, dest.priority)
137 result = dest.export(context)
138 results.append(result)
140 # Add result to context for subsequent destinations
141 context.add_result(dest.name, result)
143 if result.success:
144 _logger.info(
145 "Export succeeded to %s, stopping (first_success strategy)",
146 dest.name,
147 )
148 break
149 _logger.warning("Export to %s failed: %s", dest.name, result.error_message)
151 if not any(r.success for r in results):
152 _logger.error(
153 "All %d destination(s) failed (first_success strategy)",
154 len(results),
155 )
157 return results
160def _strategy_best_effort(
161 destinations: list[ExportDestination],
162 context: ExportContext,
163) -> list[ExportResult]:
164 """Try all, succeed if any succeed.
166 Export to all destinations regardless of individual failures.
167 The strategy is considered successful if at least one destination
168 succeeds.
170 Parameters
171 ----------
172 destinations
173 List of destinations to export to
174 context
175 Export context
177 Returns
178 -------
179 list[ExportResult]
180 Results from all destinations
181 """
182 results = []
184 for dest in destinations:
185 _logger.info("Exporting to %s (priority=%d)...", dest.name, dest.priority)
186 result = dest.export(context)
187 results.append(result)
189 # Add result to context for subsequent destinations
190 context.add_result(dest.name, result)
192 if result.success:
193 _logger.info("Export to %s succeeded", dest.name)
194 else:
195 _logger.warning("Export to %s failed: %s", dest.name, result.error_message)
197 # Check overall success
198 success_count = sum(1 for r in results if r.success)
199 if success_count > 0:
200 _logger.info(
201 "Export succeeded to %d/%d destination(s) (best_effort strategy)",
202 success_count,
203 len(results),
204 )
205 else:
206 _logger.error(
207 "All %d destination(s) failed (best_effort strategy)",
208 len(results),
209 )
211 return results