orca-runtime-python 0.1.21__tar.gz → 0.1.25__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (24) hide show
  1. {orca_runtime_python-0.1.21 → orca_runtime_python-0.1.25}/PKG-INFO +1 -1
  2. {orca_runtime_python-0.1.21 → orca_runtime_python-0.1.25}/orca_runtime_python/__init__.py +1 -1
  3. {orca_runtime_python-0.1.21 → orca_runtime_python-0.1.25}/orca_runtime_python/machine.py +2 -2
  4. {orca_runtime_python-0.1.21 → orca_runtime_python-0.1.25}/orca_runtime_python/parser.py +12 -0
  5. {orca_runtime_python-0.1.21 → orca_runtime_python-0.1.25}/orca_runtime_python/types.py +1 -1
  6. {orca_runtime_python-0.1.21 → orca_runtime_python-0.1.25}/orca_runtime_python.egg-info/PKG-INFO +1 -1
  7. {orca_runtime_python-0.1.21 → orca_runtime_python-0.1.25}/pyproject.toml +1 -1
  8. {orca_runtime_python-0.1.21 → orca_runtime_python-0.1.25}/tests/test_guards.py +124 -0
  9. {orca_runtime_python-0.1.21 → orca_runtime_python-0.1.25}/README.md +0 -0
  10. {orca_runtime_python-0.1.21 → orca_runtime_python-0.1.25}/orca_runtime_python/bus.py +0 -0
  11. {orca_runtime_python-0.1.21 → orca_runtime_python-0.1.25}/orca_runtime_python/effects.py +0 -0
  12. {orca_runtime_python-0.1.21 → orca_runtime_python-0.1.25}/orca_runtime_python/logging.py +0 -0
  13. {orca_runtime_python-0.1.21 → orca_runtime_python-0.1.25}/orca_runtime_python/persistence.py +0 -0
  14. {orca_runtime_python-0.1.21 → orca_runtime_python-0.1.25}/orca_runtime_python.egg-info/SOURCES.txt +0 -0
  15. {orca_runtime_python-0.1.21 → orca_runtime_python-0.1.25}/orca_runtime_python.egg-info/dependency_links.txt +0 -0
  16. {orca_runtime_python-0.1.21 → orca_runtime_python-0.1.25}/orca_runtime_python.egg-info/requires.txt +0 -0
  17. {orca_runtime_python-0.1.21 → orca_runtime_python-0.1.25}/orca_runtime_python.egg-info/top_level.txt +0 -0
  18. {orca_runtime_python-0.1.21 → orca_runtime_python-0.1.25}/setup.cfg +0 -0
  19. {orca_runtime_python-0.1.21 → orca_runtime_python-0.1.25}/tests/test_actions_timeouts.py +0 -0
  20. {orca_runtime_python-0.1.21 → orca_runtime_python-0.1.25}/tests/test_ignored_events.py +0 -0
  21. {orca_runtime_python-0.1.21 → orca_runtime_python-0.1.25}/tests/test_md_parser.py +0 -0
  22. {orca_runtime_python-0.1.21 → orca_runtime_python-0.1.25}/tests/test_parallel.py +0 -0
  23. {orca_runtime_python-0.1.21 → orca_runtime_python-0.1.25}/tests/test_production_hardening.py +0 -0
  24. {orca_runtime_python-0.1.21 → orca_runtime_python-0.1.25}/tests/test_snapshot_restore.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: orca-runtime-python
3
- Version: 0.1.21
3
+ Version: 0.1.25
4
4
  Summary: Python async runtime for Orca state machines
5
5
  License: Apache-2.0
6
6
  Project-URL: Homepage, https://github.com/orca-lang/orca-lang
@@ -33,7 +33,7 @@ from .persistence import PersistenceAdapter, AsyncPersistenceAdapter, FilePersis
33
33
 
34
34
  from .logging import LogSink, FileSink, ConsoleSink, MultiSink
35
35
 
36
- __version__ = "0.1.0"
36
+ __version__ = "0.1.25"
37
37
 
38
38
  __all__ = [
39
39
  # Types
@@ -717,10 +717,10 @@ class OrcaMachine:
717
717
  """Resolve a ValueRef to its Python value."""
718
718
  return ref.value
719
719
 
720
- def _eval_compare(self, op: str, left: VariableRef, right: ValueRef) -> bool:
720
+ def _eval_compare(self, op: str, left: VariableRef, right: "ValueRef | VariableRef") -> bool:
721
721
  """Evaluate a comparison guard."""
722
722
  lhs = self._resolve_variable(left)
723
- rhs = self._resolve_value(right)
723
+ rhs = self._resolve_variable(right) if isinstance(right, VariableRef) else self._resolve_value(right)
724
724
 
725
725
  # Try numeric comparison
726
726
  try:
@@ -556,6 +556,14 @@ def _parse_guard_expression(input_str: str) -> GuardExpression:
556
556
  return GuardNot(expr=parse_primary())
557
557
  return parse_primary()
558
558
 
559
+ def _lookahead_is_var_path() -> bool:
560
+ """Check if current position starts a variable path (ident.ident...)."""
561
+ return (
562
+ pos[0] + 1 < len(tokens)
563
+ and tokens[pos[0]].type == "ident"
564
+ and tokens[pos[0] + 1].type == "dot"
565
+ )
566
+
559
567
  def parse_primary() -> GuardExpression:
560
568
  tok = peek()
561
569
 
@@ -593,6 +601,10 @@ def _parse_guard_expression(input_str: str) -> GuardExpression:
593
601
  # Comparison operator
594
602
  if peek().type == "op":
595
603
  op = advance().value
604
+ # RHS: variable path (ctx.foo) or literal value
605
+ if peek().type == "ident" and _lookahead_is_var_path():
606
+ right_ref = parse_var_path()
607
+ return GuardCompare(op=_map_op(op), left=var_path, right=right_ref)
596
608
  right = parse_value()
597
609
  # Special case: != null and == null
598
610
  if right.type == "null":
@@ -124,7 +124,7 @@ class GuardCompare(GuardExpression):
124
124
  """Comparison guard: left op right"""
125
125
  op: str # eq, ne, lt, gt, le, ge
126
126
  left: VariableRef
127
- right: ValueRef
127
+ right: "ValueRef | VariableRef"
128
128
 
129
129
 
130
130
  @dataclass
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: orca-runtime-python
3
- Version: 0.1.21
3
+ Version: 0.1.25
4
4
  Summary: Python async runtime for Orca state machines
5
5
  License: Apache-2.0
6
6
  Project-URL: Homepage, https://github.com/orca-lang/orca-lang
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "orca-runtime-python"
7
- version = "0.1.21"
7
+ version = "0.1.25"
8
8
  description = "Python async runtime for Orca state machines"
9
9
  readme = "README.md"
10
10
  license = {text = "Apache-2.0"}
@@ -12,6 +12,7 @@ from orca_runtime_python.types import (
12
12
  GuardOr,
13
13
  GuardNot,
14
14
  GuardNullcheck,
15
+ VariableRef,
15
16
  )
16
17
 
17
18
 
@@ -126,6 +127,30 @@ def test_parse_string_compare():
126
127
  assert guard.right.value == "pending", f"Expected value 'pending', got {guard.right.value}"
127
128
 
128
129
 
130
+ def test_parse_var_to_var_compare():
131
+ defn = parse_orca_md(orca_md("ctx.score >= ctx.threshold", "score"))
132
+ guard = defn.guards["g"]
133
+ assert isinstance(guard, GuardCompare), f"Expected GuardCompare, got {type(guard)}"
134
+ assert guard.op == "ge", f"Expected op 'ge', got '{guard.op}'"
135
+ assert guard.left.path == ["ctx", "score"]
136
+ assert isinstance(guard.right, VariableRef), f"Expected VariableRef on RHS, got {type(guard.right)}"
137
+ assert guard.right.path == ["ctx", "threshold"]
138
+
139
+
140
+ def test_parse_var_to_var_and():
141
+ defn = parse_orca_md(orca_md("ctx.a > ctx.b and ctx.c < ctx.d", "a"))
142
+ guard = defn.guards["g"]
143
+ assert isinstance(guard, GuardAnd), f"Expected GuardAnd, got {type(guard)}"
144
+ left = guard.left
145
+ right = guard.right
146
+ assert isinstance(left, GuardCompare)
147
+ assert isinstance(left.right, VariableRef)
148
+ assert left.right.path == ["ctx", "b"]
149
+ assert isinstance(right, GuardCompare)
150
+ assert isinstance(right.right, VariableRef)
151
+ assert right.right.path == ["ctx", "d"]
152
+
153
+
129
154
  # ---- Evaluator tests ----
130
155
 
131
156
  async def _test_eval_compare_pass():
@@ -252,6 +277,86 @@ async def _test_eval_false_literal():
252
277
  assert result.guard_failed is True, f"Expected guard_failed"
253
278
 
254
279
 
280
+ def _orca_md_multi_ctx(guard_expr: str, fields: list[tuple[str, str, str]]) -> str:
281
+ """Helper with multiple context fields: [(name, type, default), ...]."""
282
+ rows = "\n".join(f"| {n} | {t} | {d} |" for n, t, d in fields)
283
+ return f"""# machine test
284
+
285
+ ## context
286
+
287
+ | Field | Type | Default |
288
+ |-------|------|---------|
289
+ {rows}
290
+
291
+ ## events
292
+
293
+ - GO
294
+
295
+ ## state idle [initial]
296
+ > Idle state
297
+
298
+ ## state done [final]
299
+ > Done state
300
+
301
+ ## guards
302
+
303
+ | Name | Expression |
304
+ |------|------------|
305
+ | g | `{guard_expr}` |
306
+
307
+ ## transitions
308
+
309
+ | Source | Event | Target | Guard |
310
+ |--------|-------|--------|-------|
311
+ | idle | GO | done | g |
312
+ """
313
+
314
+
315
+ async def _test_eval_var_to_var_ge_pass():
316
+ defn = parse_orca_md(_orca_md_multi_ctx(
317
+ "ctx.score >= ctx.threshold",
318
+ [("score", "number", "95"), ("threshold", "number", "80")],
319
+ ))
320
+ machine = OrcaMachine(defn, event_bus=EventBus())
321
+ await machine.start()
322
+ result = await machine.send("GO")
323
+ assert result.taken is True, f"Expected transition taken, got: {result.error}"
324
+
325
+
326
+ async def _test_eval_var_to_var_ge_fail():
327
+ defn = parse_orca_md(_orca_md_multi_ctx(
328
+ "ctx.score >= ctx.threshold",
329
+ [("score", "number", "0"), ("threshold", "number", "0")],
330
+ ))
331
+ machine = OrcaMachine(defn, event_bus=EventBus(), context={"score": 50, "threshold": 80})
332
+ await machine.start()
333
+ result = await machine.send("GO")
334
+ assert result.taken is False, f"Expected transition NOT taken"
335
+ assert result.guard_failed is True, f"Expected guard_failed"
336
+
337
+
338
+ async def _test_eval_var_to_var_lt_pass():
339
+ defn = parse_orca_md(_orca_md_multi_ctx(
340
+ "ctx.retry_count < ctx.max_retries",
341
+ [("retry_count", "number", "0"), ("max_retries", "number", "0")],
342
+ ))
343
+ machine = OrcaMachine(defn, event_bus=EventBus(), context={"retry_count": 1, "max_retries": 3})
344
+ await machine.start()
345
+ result = await machine.send("GO")
346
+ assert result.taken is True, f"Expected transition taken, got: {result.error}"
347
+
348
+
349
+ async def _test_eval_var_to_var_eq():
350
+ defn = parse_orca_md(_orca_md_multi_ctx(
351
+ "ctx.actual == ctx.expected",
352
+ [("actual", "number", "0"), ("expected", "number", "0")],
353
+ ))
354
+ machine = OrcaMachine(defn, event_bus=EventBus(), context={"actual": 42, "expected": 42})
355
+ await machine.start()
356
+ result = await machine.send("GO")
357
+ assert result.taken is True, f"Expected transition taken, got: {result.error}"
358
+
359
+
255
360
  # ---- Sync test wrappers ----
256
361
 
257
362
  def test_eval_compare_pass():
@@ -299,6 +404,18 @@ def test_eval_true_literal():
299
404
  def test_eval_false_literal():
300
405
  asyncio.run(_test_eval_false_literal())
301
406
 
407
+ def test_eval_var_to_var_ge_pass():
408
+ asyncio.run(_test_eval_var_to_var_ge_pass())
409
+
410
+ def test_eval_var_to_var_ge_fail():
411
+ asyncio.run(_test_eval_var_to_var_ge_fail())
412
+
413
+ def test_eval_var_to_var_lt_pass():
414
+ asyncio.run(_test_eval_var_to_var_lt_pass())
415
+
416
+ def test_eval_var_to_var_eq():
417
+ asyncio.run(_test_eval_var_to_var_eq())
418
+
302
419
 
303
420
  if __name__ == "__main__":
304
421
  tests = [
@@ -329,6 +446,13 @@ if __name__ == "__main__":
329
446
  ("eval compare >= boundary", test_eval_compare_ge),
330
447
  ("eval true literal", test_eval_true_literal),
331
448
  ("eval false literal", test_eval_false_literal),
449
+ # Variable-to-variable comparison tests
450
+ ("parse var-to-var compare", test_parse_var_to_var_compare),
451
+ ("parse var-to-var and", test_parse_var_to_var_and),
452
+ ("eval var-to-var >= pass (95 >= 80)", test_eval_var_to_var_ge_pass),
453
+ ("eval var-to-var >= fail (50 >= 80)", test_eval_var_to_var_ge_fail),
454
+ ("eval var-to-var < pass (1 < 3)", test_eval_var_to_var_lt_pass),
455
+ ("eval var-to-var == pass (42 == 42)", test_eval_var_to_var_eq),
332
456
  ]
333
457
 
334
458
  passed = 0