@@ -21,7 +21,7 @@ def _make_stream(n: int = 3) -> ArrowTableStream:
2121 return ArrowTableStream (table , tag_columns = ["id" ])
2222
2323
24- def _make_in_memory_db ():
24+ def _make_pipeline_db ():
2525 """Return a fresh in-memory ArrowDatabase."""
2626 from orcapod .databases .in_memory_databases import InMemoryArrowDatabase
2727 return InMemoryArrowDatabase ()
@@ -32,69 +32,68 @@ class TestSideEffectFunctionPodSchema:
3232
3333 def test_sf01_ctx_stripped_from_input_schema (self ):
3434 """SF-01: 'ctx' param stripped; data params form the input schema."""
35- from orcapod .core .side_effect_function import SideEffectFunctionPod
35+ from orcapod .core .function_pod import FunctionPod
3636
3737 def my_fn (value : int , ctx : InvocationContext ) -> str :
3838 return f"result_{ value } "
3939
40- pod = SideEffectFunctionPod (my_fn , output_keys = ["result" ])
40+ pod = FunctionPod . from_fn (my_fn , output_keys = ["result" ], ctx_arg_name = "ctx" )
4141
4242 # Input schema excludes 'ctx'
43- assert "ctx" not in pod .input_data_schema
44- assert "value" in pod .input_data_schema
45- assert pod .input_data_schema ["value" ] == int
43+ assert "ctx" not in pod ._data_function . input_data_schema
44+ assert "value" in pod ._data_function . input_data_schema
45+ assert pod ._data_function . input_data_schema ["value" ] == int
4646
4747 # Output schema has the declared key
48- assert "result" in pod .output_data_schema
49- assert pod .output_data_schema ["result" ] == str
48+ assert "result" in pod ._data_function . output_data_schema
49+ assert pod ._data_function . output_data_schema ["result" ] == str
5050
5151 def test_sf02_custom_ctx_arg_name (self ):
5252 """SF-02: ctx_arg_name='context' — stripped and injected by correct name."""
53- from orcapod .core .side_effect_function import SideEffectFunctionPod
53+ from orcapod .core .function_pod import FunctionPod
5454
5555 def my_fn (value : int , context : InvocationContext ) -> str :
5656 return f"r_{ value } "
5757
58- pod = SideEffectFunctionPod (my_fn , output_keys = ["result" ], ctx_arg_name = "context" )
59- assert "context" not in pod .input_data_schema
60- assert "value" in pod .input_data_schema
58+ pod = FunctionPod . from_fn (my_fn , output_keys = ["result" ], ctx_arg_name = "context" )
59+ assert "context" not in pod ._data_function . input_data_schema
60+ assert "value" in pod ._data_function . input_data_schema
6161
6262 def test_sf03_missing_ctx_arg_raises_at_construction (self ):
6363 """SF-03: Missing ctx_arg_name raises ValueError at construction time."""
64- from orcapod .core .side_effect_function import SideEffectFunctionPod
64+ from orcapod .core .function_pod import FunctionPod
6565
6666 def my_fn (value : int ) -> str :
6767 return str (value )
6868
6969 with pytest .raises (ValueError , match = "ctx_arg_name" ):
70- SideEffectFunctionPod (my_fn , output_keys = ["result" ])
71- # Default ctx_arg_name="ctx" is missing from my_fn's signature
70+ FunctionPod .from_fn (my_fn , output_keys = ["result" ], ctx_arg_name = "ctx" )
7271
7372 def test_sf10_node_uri_shape (self ):
74- """SF-10: uri[0]=='side_effect_function', uri[1: ]==data_function.uri , len==5."""
75- from orcapod .core .side_effect_function import SideEffectFunctionPod
73+ """SF-10: uri[0]=='side_effect_function', uri[-1 ]=='python.function.v0' , len==5."""
74+ from orcapod .core .function_pod import FunctionPod
7675
7776 def my_fn (value : int , ctx : InvocationContext ) -> str :
7877 return str (value )
7978
80- pod = SideEffectFunctionPod (my_fn , output_keys = ["result" ])
79+ pod = FunctionPod . from_fn (my_fn , output_keys = ["result" ], ctx_arg_name = "ctx" )
8180 assert pod .uri [0 ] == "side_effect_function"
8281 assert pod .uri [- 1 ] == "python.function.v0"
8382 assert len (pod .uri ) == 5
8483 assert pod .uri [3 ] == "v1"
8584
8685
8786class TestSideEffectFunctionPodStreamStandalone :
88- """SF-04, SF-05: standalone execution via SideEffectFunctionPodStream ."""
87+ """SF-04, SF-05: standalone execution via FunctionPodStream ."""
8988
9089 def test_sf04_iter_data_returns_correct_output (self ):
9190 """SF-04: iter_data() returns correct (tag, output_data) per row."""
92- from orcapod .core .side_effect_function import SideEffectFunctionPod
91+ from orcapod .core .function_pod import FunctionPod
9392
9493 def my_fn (value : int , ctx : InvocationContext ) -> str :
9594 return f"v{ value } "
9695
97- pod = SideEffectFunctionPod (my_fn , output_keys = ["result" ])
96+ pod = FunctionPod . from_fn (my_fn , output_keys = ["result" ], ctx_arg_name = "ctx" )
9897 stream = _make_stream (3 )
9998 rows = list (pod .process (stream ).iter_data ())
10099
@@ -107,15 +106,15 @@ def my_fn(value: int, ctx: InvocationContext) -> str:
107106
108107 def test_sf05_invocation_context_fields_standalone (self ):
109108 """SF-05: InvocationContext has pod_name, non-empty hash, pipeline_run_id=None."""
110- from orcapod .core .side_effect_function import SideEffectFunctionPod
109+ from orcapod .core .function_pod import FunctionPod
111110
112111 received_ctx : list [InvocationContext ] = []
113112
114113 def my_fn (value : int , ctx : InvocationContext ) -> str :
115114 received_ctx .append (ctx )
116115 return str (value )
117116
118- pod = SideEffectFunctionPod (my_fn , output_keys = ["result" ])
117+ pod = FunctionPod . from_fn (my_fn , output_keys = ["result" ], ctx_arg_name = "ctx" )
119118 stream = _make_stream (1 )
120119 list (pod .process (stream ).iter_data ())
121120
@@ -129,15 +128,15 @@ def my_fn(value: int, ctx: InvocationContext) -> str:
129128
130129 def test_sf05b_async_fn_routed_through_sync_execute (self ):
131130 """SF-05b: async user function executed correctly via _call_async_sync."""
132- from orcapod .core .side_effect_function import SideEffectFunctionPod
131+ from orcapod .core .function_pod import FunctionPod
133132
134133 import asyncio
135134
136135 async def my_async_fn (value : int , ctx : InvocationContext ) -> str :
137- await asyncio .sleep (0 ) # yield control to ensure coroutine runs correctly
136+ await asyncio .sleep (0 )
138137 return f"async_{ value } "
139138
140- pod = SideEffectFunctionPod (my_async_fn , output_keys = ["result" ])
139+ pod = FunctionPod . from_fn (my_async_fn , output_keys = ["result" ], ctx_arg_name = "ctx" )
141140 stream = _make_stream (2 )
142141 rows = list (pod .process (stream ).iter_data ())
143142
@@ -147,11 +146,12 @@ async def my_async_fn(value: int, ctx: InvocationContext) -> str:
147146
148147
149148class TestSideEffectFunctionJobNode :
150- """SF-06, SF-07, SF-09: DB-backed sync execution."""
149+ """SF-06, SF-07, SF-09: DB-backed sync execution via FunctionJobNode ."""
151150
152151 def test_sf06_output_cached_after_first_run (self ):
153152 """SF-06: Output cached; second run returns cached result without re-calling fn."""
154- from orcapod .core .side_effect_function import SideEffectFunctionPod , SideEffectFunctionJobNode
153+ from orcapod .core .function_pod import FunctionPod
154+ from orcapod .core .nodes .function_node import FunctionJobNode
155155
156156 call_count = 0
157157
@@ -160,19 +160,19 @@ def my_fn(value: int, ctx: InvocationContext) -> str:
160160 call_count += 1
161161 return f"r{ value } "
162162
163- pod = SideEffectFunctionPod (my_fn , output_keys = ["result" ])
163+ pod = FunctionPod . from_fn (my_fn , output_keys = ["result" ], ctx_arg_name = "ctx" )
164164 stream = _make_stream (2 )
165- result_db = _make_in_memory_db ()
165+ pipeline_db = _make_pipeline_db ()
166166
167- node1 = SideEffectFunctionJobNode ( pod = pod , input_stream = stream )
168- node1 .attach_databases (result_database = result_db )
167+ node1 = FunctionJobNode ( function_pod = pod , input_stream = stream )
168+ node1 .attach_databases (pipeline_database = pipeline_db )
169169 results1 = node1 .execute (stream )
170170 assert len (results1 ) == 2
171171 assert call_count == 2
172172
173- # Second run — same pod, same data, same DBs — fn must NOT be called again
174- node2 = SideEffectFunctionJobNode ( pod = pod , input_stream = stream )
175- node2 .attach_databases (result_database = result_db )
173+ # Second run — same pod, same data, same DB — fn must NOT be called again
174+ node2 = FunctionJobNode ( function_pod = pod , input_stream = stream )
175+ node2 .attach_databases (pipeline_database = pipeline_db )
176176 results2 = node2 .execute (stream )
177177 assert len (results2 ) == 2
178178 assert call_count == 2 # NOT incremented — cache hit
@@ -182,70 +182,68 @@ def my_fn(value: int, ctx: InvocationContext) -> str:
182182 assert d1 .as_dict ()["result" ] == d2 .as_dict ()["result" ]
183183
184184 def test_sf07_data_function_accessible_and_uri_consistent (self ):
185- """SF-07: pod._data_function is a PythonDataFunction; uri = ( 'side_effect_function',) + data_function.uri ."""
186- from orcapod .core .side_effect_function import SideEffectFunctionPod
185+ """SF-07: pod._data_function is a PythonDataFunction; uri starts with 'side_effect_function'."""
186+ from orcapod .core .function_pod import FunctionPod
187187 from orcapod .core .data_function import PythonDataFunction
188188
189189 def my_fn (value : int , ctx : InvocationContext ) -> str :
190190 return f"r{ value } "
191191
192- pod = SideEffectFunctionPod (my_fn , output_keys = ["result" ])
192+ pod = FunctionPod . from_fn (my_fn , output_keys = ["result" ], ctx_arg_name = "ctx" )
193193 assert isinstance (pod ._data_function , PythonDataFunction )
194194 assert pod .uri [0 ] == "side_effect_function"
195- assert pod .uri [ 1 :] == pod . _data_function . uri
195+ assert pod ._ctx_arg_name == "ctx"
196196
197- def test_sf09_on_error_log_reraises (self ):
198- """SF-09: on_error='log' — exception logged then always re-raised ."""
199- from orcapod .side_effects import SideEffectPodConfig
200- from orcapod .core .side_effect_function import SideEffectFunctionPod , SideEffectFunctionJobNode
197+ def test_sf09_on_error_reraises (self ):
198+ """SF-09: exceptions from user function always propagate ."""
199+ from orcapod .core . function_pod import FunctionPod
200+ from orcapod .core .nodes . function_node import FunctionJobNode
201201
202202 def my_fn (value : int , ctx : InvocationContext ) -> str :
203203 raise RuntimeError ("test error" )
204204
205- cfg = SideEffectPodConfig (on_error = "log" )
206- pod = SideEffectFunctionPod (my_fn , output_keys = ["result" ], config = cfg )
205+ pod = FunctionPod .from_fn (my_fn , output_keys = ["result" ], ctx_arg_name = "ctx" )
207206 stream = _make_stream (1 )
208- result_db = _make_in_memory_db ()
209- node = SideEffectFunctionJobNode ( pod = pod , input_stream = stream )
210- node .attach_databases (result_database = result_db )
207+ pipeline_db = _make_pipeline_db ()
208+ node = FunctionJobNode ( function_pod = pod , input_stream = stream )
209+ node .attach_databases (pipeline_database = pipeline_db )
211210
212211 # Must propagate — no silent row suppression
213212 with pytest .raises (RuntimeError , match = "test error" ):
214- node .execute (stream )
213+ node .execute (stream , error_policy = "fail_fast" )
215214
216215
217216class TestSideEffectFunctionPodDecorator :
218217 """SF-11: @side_effect_function_pod decorator."""
219218
220219 def test_sf11_decorator_creates_correct_pod (self ):
221- """SF-11: Decorator creates a SideEffectFunctionPod with correct URI."""
222- from orcapod .core .side_effect_function import (
223- SideEffectFunctionPod ,
224- side_effect_function_pod ,
225- )
220+ """SF-11: Decorator creates a FunctionPod with correct URI."""
221+ from orcapod .core .function_pod import FunctionPod , side_effect_function_pod
226222
227223 @side_effect_function_pod (output_keys = ["result" ])
228224 def my_fn (value : int , ctx : InvocationContext ) -> str :
229225 return str (value )
230226
231- assert isinstance (my_fn , SideEffectFunctionPod )
227+ assert isinstance (my_fn , FunctionPod )
232228 assert my_fn .uri [0 ] == "side_effect_function"
233229 assert my_fn .canonical_function_name == "my_fn"
234230
235231 def test_sf11_decorator_accessible_from_public_api (self ):
236- """SF-11: Decorator and pod class accessible from orcapod top-level."""
232+ """SF-11: Decorator accessible from orcapod top-level; class removed ."""
237233 import orcapod
238234 assert hasattr (orcapod , "side_effect_function_pod" )
239- assert hasattr (orcapod , "SideEffectFunctionPod" )
235+ assert hasattr (orcapod , "FunctionPod" )
236+ # SideEffectFunctionPod class is no longer exported (unified into FunctionPod)
237+ assert not hasattr (orcapod , "SideEffectFunctionPod" )
240238
241239
242240class TestSideEffectFunctionPodPipelineIntegration :
243241 """SF-12: Full pipeline compilation and execution."""
244242
245243 def test_sf12_pipeline_compilation_and_execution (self ):
246- """SF-12: SideEffectFunctionJobNode compiled, fn called, caching works ."""
244+ """SF-12: FunctionJobNode compiled for ctx-aware pod , fn called, ctx received ."""
247245 from orcapod .pipeline .job import PipelineJob
248- from orcapod .core .side_effect_function import SideEffectFunctionPod , SideEffectFunctionJobNode
246+ from orcapod .core .function_pod import FunctionPod
249247 from orcapod .core .sources .dict_source import DictSource
250248
251249 received_ctx : list [InvocationContext ] = []
@@ -254,8 +252,8 @@ def transform(value: int, ctx: InvocationContext) -> str:
254252 received_ctx .append (ctx )
255253 return f"result_{ value } "
256254
257- pod = SideEffectFunctionPod (transform , output_keys = ["result" ])
258- db = _make_in_memory_db ()
255+ pod = FunctionPod . from_fn (transform , output_keys = ["result" ], ctx_arg_name = "ctx" )
256+ db = _make_pipeline_db ()
259257
260258 with PipelineJob (name = "test_sef" , store = db ) as job :
261259 source = DictSource (
@@ -278,7 +276,7 @@ def transform(value: int, ctx: InvocationContext) -> str:
278276 def test_sf12_second_pipeline_run_uses_cache (self ):
279277 """SF-12: Second pipeline run uses cached output; fn not called again."""
280278 from orcapod .pipeline .job import PipelineJob
281- from orcapod .core .side_effect_function import SideEffectFunctionPod
279+ from orcapod .core .function_pod import FunctionPod
282280 from orcapod .core .sources .dict_source import DictSource
283281
284282 call_count = 0
@@ -288,8 +286,8 @@ def transform(value: int, ctx: InvocationContext) -> str:
288286 call_count += 1
289287 return f"r{ value } "
290288
291- pod = SideEffectFunctionPod (transform , output_keys = ["result" ])
292- db = _make_in_memory_db ()
289+ pod = FunctionPod . from_fn (transform , output_keys = ["result" ], ctx_arg_name = "ctx" )
290+ db = _make_pipeline_db ()
293291 source_data = [{"id" : 0 , "value" : 10 }, {"id" : 1 , "value" : 20 }]
294292
295293 with PipelineJob (name = "test_sef_cache" , store = db ) as job1 :
@@ -311,7 +309,8 @@ class TestSideEffectFunctionJobNodeAsync:
311309 def test_sf13_async_execute_basic (self ):
312310 """SF-13: async_execute processes all rows, writes cache, returns correct output."""
313311 import asyncio
314- from orcapod .core .side_effect_function import SideEffectFunctionPod , SideEffectFunctionJobNode
312+ from orcapod .core .function_pod import FunctionPod
313+ from orcapod .core .nodes .function_node import FunctionJobNode
315314 from orcapod .channels import Channel
316315
317316 call_count = 0
@@ -321,11 +320,11 @@ def my_fn(value: int, ctx: InvocationContext) -> str:
321320 call_count += 1
322321 return f"async_{ value } "
323322
324- pod = SideEffectFunctionPod (my_fn , output_keys = ["result" ])
323+ pod = FunctionPod . from_fn (my_fn , output_keys = ["result" ], ctx_arg_name = "ctx" )
325324 stream = _make_stream (3 )
326- result_db = _make_in_memory_db ()
327- node = SideEffectFunctionJobNode ( pod = pod , input_stream = stream )
328- node .attach_databases (result_database = result_db )
325+ pipeline_db = _make_pipeline_db ()
326+ node = FunctionJobNode ( function_pod = pod , input_stream = stream )
327+ node .attach_databases (pipeline_database = pipeline_db )
329328
330329 async def _run ():
331330 ch_in = Channel (buffer_size = 10 )
@@ -339,7 +338,7 @@ async def feed():
339338 await asyncio .gather (
340339 feed (),
341340 node .async_execute (
342- [ ch_in .reader ] , ch_out .writer , run_id = "test-run-sf13"
341+ ch_in .reader , ch_out .writer , run_id = "test-run-sf13"
343342 ),
344343 )
345344 return await ch_out .reader .collect ()
0 commit comments