Skip to content

Commit 32ae219

Browse files
committed
Add dummy example with CallableModel making callable models
Signed-off-by: Nijat Khanbabayev <nijat.khanbabayev@cubistsystematic.com>
1 parent 9fb2a92 commit 32ae219

File tree

3 files changed

+78
-3
lines changed

3 files changed

+78
-3
lines changed

ccflow/tests/evaluators/test_common.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
combine_evaluators,
3030
get_dependency_graph,
3131
)
32-
from ccflow.tests.local_helpers import build_nested_graph_chain
32+
from ccflow.tests.local_helpers import build_meta_sensor_planner, build_nested_graph_chain
3333

3434
from .util import CircularModel, MyDateCallable, MyDateRangeCallable, MyRaisingCallable, NodeModel, ResultModel
3535

@@ -220,6 +220,22 @@ def test_logging_options_nested(self):
220220
self.assertIn("End evaluation of __call__", captured.records[2].getMessage())
221221
self.assertIn("time elapsed", captured.records[2].getMessage())
222222

223+
def test_meta_callable_logged_with_evaluator(self):
224+
"""Meta callables can spin up request-scoped specialists and still inherit evaluator instrumentation."""
225+
SensorQuery, MetaSensorPlanner, captured = build_meta_sensor_planner()
226+
evaluator = LoggingEvaluator(log_level=logging.INFO, verbose=False)
227+
request = SensorQuery(sensor_type="pressure-valve", site="orbital-lab", window=4)
228+
meta = MetaSensorPlanner(warm_start=2)
229+
with FlowOptionsOverride(options=FlowOptions(evaluator=evaluator)):
230+
with self.assertLogs(level=logging.INFO) as captured_logs:
231+
result = meta(request)
232+
self.assertEqual(result.value, "planner:orbital-lab:pressure-valve:6")
233+
start_messages = [record.getMessage() for record in captured_logs.records if "Start evaluation" in record.getMessage()]
234+
self.assertEqual(len(start_messages), 2)
235+
self.assertTrue(any("MetaSensorPlanner" in msg for msg in start_messages))
236+
specialist_name = captured["callable_cls"].__name__
237+
self.assertTrue(any(specialist_name in msg for msg in start_messages))
238+
223239

224240
class SubContext(DateContext):
225241
pass

ccflow/tests/local_helpers.py

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,54 @@
11
"""Shared helpers for constructing local-scope contexts/models in tests."""
22

3-
from typing import ClassVar, Tuple, Type
3+
from typing import ClassVar, Dict, Tuple, Type
44

55
from ccflow import CallableModel, ContextBase, Flow, GenericResult, GraphDepList, NullContext
66

77

8+
def build_meta_sensor_planner():
9+
"""Return a (SensorQuery, MetaSensorPlanner, captured) tuple for meta-callable tests."""
10+
11+
captured: Dict[str, Type] = {}
12+
13+
class SensorQuery(ContextBase):
14+
sensor_type: str
15+
site: str
16+
window: int
17+
18+
class MetaSensorPlanner(CallableModel):
19+
warm_start: int = 2
20+
21+
@Flow.call
22+
def __call__(self, context: SensorQuery) -> GenericResult:
23+
# Define request-scoped specialist wiring with a bespoke context/model pair.
24+
class SpecialistContext(ContextBase):
25+
sensor_type: str
26+
window: int
27+
pipeline: str
28+
29+
class SpecialistCallable(CallableModel):
30+
pipeline: str
31+
32+
@Flow.call
33+
def __call__(self, context: SpecialistContext) -> GenericResult:
34+
payload = f"{self.pipeline}:{context.sensor_type}:{context.window}"
35+
return GenericResult(value=payload)
36+
37+
captured["context_cls"] = SpecialistContext
38+
captured["callable_cls"] = SpecialistCallable
39+
40+
window = context.window + self.warm_start
41+
local_context = SpecialistContext(
42+
sensor_type=context.sensor_type,
43+
window=window,
44+
pipeline=f"{context.site}-calibration",
45+
)
46+
specialist = SpecialistCallable(pipeline=f"planner:{context.site}")
47+
return specialist(local_context)
48+
49+
return SensorQuery, MetaSensorPlanner, captured
50+
51+
852
def build_local_callable(name: str = "LocalCallable") -> Type[CallableModel]:
953
class _LocalCallable(CallableModel):
1054
@Flow.call

ccflow/tests/test_callable.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
WrapperModel,
2424
)
2525
from ccflow.local_persistence import LOCAL_ARTIFACTS_MODULE_NAME
26-
from ccflow.tests.local_helpers import build_local_callable, build_local_context
26+
from ccflow.tests.local_helpers import build_local_callable, build_local_context, build_meta_sensor_planner
2727

2828

2929
def _find_registered_name(module, cls):
@@ -659,6 +659,21 @@ def __call__(self, context: LocalContext) -> GenericResult:
659659
else:
660660
self.assertEqual(result.value, ctx_instance.value * (label + 1))
661661

662+
def test_meta_callable_builds_dynamic_specialist(self):
663+
SensorQuery, MetaSensorPlanner, captured = build_meta_sensor_planner()
664+
request = SensorQuery(sensor_type="wind-turbine", site="ridge-line", window=5)
665+
meta = MetaSensorPlanner(warm_start=3)
666+
result = meta(request)
667+
self.assertEqual(result.value, "planner:ridge-line:wind-turbine:8")
668+
669+
locals_module = sys.modules[LOCAL_ARTIFACTS_MODULE_NAME]
670+
SpecialistContext = captured["context_cls"]
671+
SpecialistCallable = captured["callable_cls"]
672+
self.assertEqual(SpecialistContext.__module__, LOCAL_ARTIFACTS_MODULE_NAME)
673+
self.assertEqual(SpecialistCallable.__module__, LOCAL_ARTIFACTS_MODULE_NAME)
674+
self.assertIs(getattr(locals_module, SpecialistContext.__qualname__), SpecialistContext)
675+
self.assertIs(getattr(locals_module, SpecialistCallable.__qualname__), SpecialistCallable)
676+
662677

663678
class TestWrapperModel(TestCase):
664679
def test_wrapper(self):

0 commit comments

Comments
 (0)