mrmd-python 0.3.5__tar.gz → 0.3.7__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.5
3
+ Version: 0.3.7
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.5"
3
+ version = "0.3.7"
4
4
  description = "Python runtime server implementing the MRMD Runtime Protocol (MRP)"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -0,0 +1,5 @@
1
+ """Allow running as python -m mrmd_python."""
2
+ from .cli import main
3
+
4
+ if __name__ == "__main__":
5
+ main()
@@ -401,6 +401,18 @@ class DaemonRuntimeClient:
401
401
  """Check if the daemon runtime is still running."""
402
402
  return is_runtime_alive(self.runtime_id)
403
403
 
404
+ def interrupt(self) -> bool:
405
+ """Interrupt currently running code in the daemon.
406
+
407
+ Sends interrupt request to the daemon via HTTP.
408
+ Returns True if request was sent successfully.
409
+ """
410
+ try:
411
+ result = self._post("/mrp/v1/interrupt", {"session": "default"})
412
+ return result.get("interrupted", False)
413
+ except Exception:
414
+ return False
415
+
404
416
  def _parse_execute_result(self, data: dict) -> ExecuteResult:
405
417
  """Parse execute result from HTTP response."""
406
418
  error = None
@@ -568,9 +568,29 @@ class MRPServer:
568
568
  return JSONResponse({"cancelled": False, "error": "No pending input request"})
569
569
 
570
570
  async def handle_interrupt(self, request: Request) -> JSONResponse:
571
- """POST /interrupt"""
572
- # TODO: Implement actual interrupt via signal
573
- return JSONResponse({"interrupted": True})
571
+ """POST /interrupt
572
+
573
+ Interrupt currently running code in a session.
574
+ Sends SIGINT to subprocess workers or sets interrupt flag for local workers.
575
+ """
576
+ body = await request.json()
577
+ session_id = body.get("session", "default")
578
+
579
+ # Get the worker for this session (don't create if doesn't exist)
580
+ session = self.session_manager.get_session(session_id)
581
+ if not session:
582
+ return JSONResponse({"interrupted": False, "error": "Session not found"})
583
+
584
+ worker = self.session_manager.workers.get(session_id)
585
+ if not worker:
586
+ return JSONResponse({"interrupted": False, "error": "Worker not found"})
587
+
588
+ # Call interrupt on the worker
589
+ try:
590
+ interrupted = worker.interrupt()
591
+ return JSONResponse({"interrupted": interrupted})
592
+ except Exception as e:
593
+ return JSONResponse({"interrupted": False, "error": str(e)})
574
594
 
575
595
  async def handle_complete(self, request: Request) -> JSONResponse:
576
596
  """POST /complete"""
@@ -419,6 +419,20 @@ class SubprocessWorker:
419
419
  self._started = False
420
420
  self._pid = None
421
421
 
422
+ def interrupt(self) -> bool:
423
+ """Send SIGINT to the subprocess to raise KeyboardInterrupt.
424
+
425
+ Returns True if signal was sent successfully.
426
+ """
427
+ if self._process and self._process.poll() is None:
428
+ try:
429
+ import signal
430
+ self._process.send_signal(signal.SIGINT)
431
+ return True
432
+ except Exception:
433
+ pass
434
+ return False
435
+
422
436
  def is_alive(self) -> bool:
423
437
  """Check if subprocess is still running."""
424
438
  return self._process is not None and self._process.poll() is None
@@ -1438,6 +1438,22 @@ class IPythonWorker:
1438
1438
  self._subprocess_worker.shutdown()
1439
1439
  self._subprocess_worker = None
1440
1440
 
1441
+ def interrupt(self) -> bool:
1442
+ """Interrupt currently running code.
1443
+
1444
+ For subprocess workers, sends SIGINT.
1445
+ For local execution, sets interrupt flag (checked in callbacks).
1446
+
1447
+ Returns True if interrupt was initiated.
1448
+ """
1449
+ if self._subprocess_worker is not None:
1450
+ return self._subprocess_worker.interrupt()
1451
+
1452
+ # For local execution, set the interrupt flag
1453
+ # This will be checked by long-running operations
1454
+ self._interrupt_requested = True
1455
+ return True
1456
+
1441
1457
  def get_info(self) -> dict:
1442
1458
  """Get info about this worker."""
1443
1459
  return {
@@ -285,7 +285,7 @@ wheels = [
285
285
 
286
286
  [[package]]
287
287
  name = "mrmd-python"
288
- version = "0.2.0"
288
+ version = "0.3.6"
289
289
  source = { editable = "." }
290
290
  dependencies = [
291
291
  { name = "black" },
File without changes
File without changes