mrmd-python 0.3.2__tar.gz → 0.3.4__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mrmd-python
3
- Version: 0.3.2
3
+ Version: 0.3.4
4
4
  Summary: Python runtime server implementing the MRMD Runtime Protocol (MRP)
5
5
  Author: mrmd contributors
6
6
  License: MIT
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "mrmd-python"
3
- version = "0.3.2"
3
+ version = "0.3.4"
4
4
  description = "Python runtime server implementing the MRMD Runtime Protocol (MRP)"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -647,8 +647,8 @@ class IPythonWorker:
647
647
  return ExecuteResult(
648
648
  success=False,
649
649
  error=ExecuteError(
650
- ename="VenvError",
651
- evalue="Could not find Python executable in venv",
650
+ type="VenvError",
651
+ message="Could not find Python executable in venv",
652
652
  traceback=[]
653
653
  )
654
654
  )
@@ -657,24 +657,37 @@ class IPythonWorker:
657
657
  start_time = time.time()
658
658
 
659
659
  # Create a wrapper script that executes the code and captures output
660
+ # Uses AST to detect trailing expressions and print their results like IPython
661
+ # Use .strip() to remove any leading/trailing whitespace from the embedded code
660
662
  wrapper_code = '''
661
- import sys
662
- import json
663
+ import sys as _sys
664
+ import ast as _ast
663
665
 
664
- # Execute the user's code
665
- _mrmd_result = None
666
- _mrmd_error = None
667
- _mrmd_stdout = ""
666
+ _code = """''' + code.replace('\\', '\\\\').replace('"""', '\\"\\"\\"') + '''""".strip()
668
667
 
669
668
  try:
670
- exec(compile("""''' + code.replace('\\', '\\\\').replace('"""', '\\"\\"\\"') + '''""", "<cell>", "exec"))
671
- except Exception as e:
672
- import traceback
673
- _mrmd_error = {
674
- "ename": type(e).__name__,
675
- "evalue": str(e),
676
- "traceback": traceback.format_exception(type(e), e, e.__traceback__)
677
- }
669
+ _tree = _ast.parse(_code)
670
+ if _tree.body and isinstance(_tree.body[-1], _ast.Expr):
671
+ # Last statement is an expression - capture its value
672
+ if len(_tree.body) > 1:
673
+ # Execute all but last statement
674
+ _exec_code = _ast.Module(body=_tree.body[:-1], type_ignores=[])
675
+ exec(compile(_exec_code, "<cell>", "exec"))
676
+ # Evaluate and print last expression
677
+ _expr_code = _ast.Expression(body=_tree.body[-1].value)
678
+ _result = eval(compile(_expr_code, "<cell>", "eval"))
679
+ if _result is not None:
680
+ print(f"Out[''' + str(self._execution_count) + ''']: " + repr(_result))
681
+ else:
682
+ # No trailing expression, just exec everything
683
+ exec(compile(_code, "<cell>", "exec"))
684
+ except SyntaxError:
685
+ # Fall back to simple exec
686
+ exec(compile(_code, "<cell>", "exec"))
687
+ except Exception as _e:
688
+ import traceback as _tb
689
+ print("".join(_tb.format_exception(type(_e), _e, _e.__traceback__)), file=_sys.stderr)
690
+ _sys.exit(1)
678
691
  '''
679
692
 
680
693
  try:
@@ -704,8 +717,8 @@ except Exception as e:
704
717
  stdout=result.stdout,
705
718
  stderr=result.stderr,
706
719
  error=ExecuteError(
707
- ename="ExecutionError",
708
- evalue=error_lines[-1] if error_lines else "Unknown error",
720
+ type="ExecutionError",
721
+ message=error_lines[-1] if error_lines else "Unknown error",
709
722
  traceback=error_lines
710
723
  ),
711
724
  executionCount=self._execution_count,
@@ -716,8 +729,8 @@ except Exception as e:
716
729
  return ExecuteResult(
717
730
  success=False,
718
731
  error=ExecuteError(
719
- ename="TimeoutError",
720
- evalue="Execution timed out after 5 minutes",
732
+ type="TimeoutError",
733
+ message="Execution timed out after 5 minutes",
721
734
  traceback=[]
722
735
  ),
723
736
  executionCount=self._execution_count,
@@ -726,8 +739,8 @@ except Exception as e:
726
739
  return ExecuteResult(
727
740
  success=False,
728
741
  error=ExecuteError(
729
- ename=type(e).__name__,
730
- evalue=str(e),
742
+ type=type(e).__name__,
743
+ message=str(e),
731
744
  traceback=[]
732
745
  ),
733
746
  executionCount=self._execution_count,
@@ -742,13 +755,17 @@ except Exception as e:
742
755
  """Execute code in subprocess with streaming output."""
743
756
  import subprocess
744
757
 
758
+ print(f"[IPythonWorker._execute_subprocess_streaming] Starting subprocess execution", flush=True)
759
+
745
760
  python_exe = self._get_venv_python()
761
+ print(f"[IPythonWorker._execute_subprocess_streaming] python_exe={python_exe}", flush=True)
762
+
746
763
  if not python_exe:
747
764
  return ExecuteResult(
748
765
  success=False,
749
766
  error=ExecuteError(
750
- ename="VenvError",
751
- evalue="Could not find Python executable in venv",
767
+ type="VenvError",
768
+ message="Could not find Python executable in venv",
752
769
  traceback=[]
753
770
  )
754
771
  )
@@ -758,10 +775,43 @@ except Exception as e:
758
775
  accumulated_stdout = ""
759
776
  accumulated_stderr = ""
760
777
 
778
+ # Wrap code to capture expression results like IPython does
779
+ # This handles cases like "import sys; sys.executable" which need to print
780
+ # Use .strip() to remove any leading/trailing whitespace from the embedded code
781
+ wrapper_code = '''
782
+ import sys as _sys
783
+ import ast as _ast
784
+
785
+ _code = """''' + code.replace('\\', '\\\\').replace('"""', '\\"\\"\\"') + '''""".strip()
786
+
787
+ # Parse to check if last statement is an expression
788
+ try:
789
+ _tree = _ast.parse(_code)
790
+ if _tree.body and isinstance(_tree.body[-1], _ast.Expr):
791
+ # Last statement is an expression - capture its value
792
+ if len(_tree.body) > 1:
793
+ # Execute all but last statement
794
+ _exec_code = _ast.Module(body=_tree.body[:-1], type_ignores=[])
795
+ exec(compile(_exec_code, "<cell>", "exec"))
796
+ # Evaluate and print last expression
797
+ _expr_code = _ast.Expression(body=_tree.body[-1].value)
798
+ _result = eval(compile(_expr_code, "<cell>", "eval"))
799
+ if _result is not None:
800
+ print(f"Out[{''' + str(self._execution_count) + '''}]: " + repr(_result))
801
+ else:
802
+ # No trailing expression, just exec everything
803
+ exec(compile(_code, "<cell>", "exec"))
804
+ except SyntaxError as _e:
805
+ # Fall back to simple exec for syntax errors in wrapper
806
+ exec(compile(_code, "<cell>", "exec"))
807
+ '''
808
+
809
+ print(f"[IPythonWorker._execute_subprocess_streaming] Running: {python_exe} -c ...", flush=True)
810
+
761
811
  try:
762
812
  # Start subprocess
763
813
  process = subprocess.Popen(
764
- [python_exe, "-c", code],
814
+ [python_exe, "-c", wrapper_code],
765
815
  stdout=subprocess.PIPE,
766
816
  stderr=subprocess.PIPE,
767
817
  text=True,
@@ -807,8 +857,8 @@ except Exception as e:
807
857
  stdout=accumulated_stdout,
808
858
  stderr=accumulated_stderr,
809
859
  error=ExecuteError(
810
- ename="ExecutionError",
811
- evalue=error_lines[-1] if error_lines else "Unknown error",
860
+ type="ExecutionError",
861
+ message=error_lines[-1] if error_lines else "Unknown error",
812
862
  traceback=error_lines
813
863
  ),
814
864
  executionCount=self._execution_count,
@@ -827,8 +877,8 @@ except Exception as e:
827
877
  return ExecuteResult(
828
878
  success=False,
829
879
  error=ExecuteError(
830
- ename=type(e).__name__,
831
- evalue=str(e),
880
+ type=type(e).__name__,
881
+ message=str(e),
832
882
  traceback=[]
833
883
  ),
834
884
  executionCount=self._execution_count,
File without changes
File without changes
File without changes