virtualshell 1.1.2__tar.gz → 1.1.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.
Files changed (62) hide show
  1. {virtualshell-1.1.2 → virtualshell-1.1.4}/.gitignore +2 -1
  2. {virtualshell-1.1.2 → virtualshell-1.1.4}/CMakeLists.txt +1 -0
  3. {virtualshell-1.1.2 → virtualshell-1.1.4}/PKG-INFO +71 -5
  4. {virtualshell-1.1.2 → virtualshell-1.1.4}/README.md +70 -4
  5. {virtualshell-1.1.2 → virtualshell-1.1.4}/cpp/include/py_bridge.hpp +0 -1
  6. {virtualshell-1.1.2 → virtualshell-1.1.4}/cpp/include/py_proxy.hpp +19 -2
  7. {virtualshell-1.1.2 → virtualshell-1.1.4}/cpp/include/virtual_shell.hpp +6 -0
  8. {virtualshell-1.1.2 → virtualshell-1.1.4}/cpp/src/binder.cpp +3 -1
  9. {virtualshell-1.1.2 → virtualshell-1.1.4}/cpp/src/powershell_process.cpp +1 -1
  10. {virtualshell-1.1.2 → virtualshell-1.1.4}/cpp/src/py_proxy.cpp +536 -30
  11. {virtualshell-1.1.2 → virtualshell-1.1.4}/cpp/src/virtual_shell.cpp +9 -1
  12. {virtualshell-1.1.2 → virtualshell-1.1.4}/pyproject.toml +1 -1
  13. {virtualshell-1.1.2 → virtualshell-1.1.4}/src/virtualshell/__init__.py +9 -1
  14. virtualshell-1.1.4/src/virtualshell/_version.py +1 -0
  15. {virtualshell-1.1.2 → virtualshell-1.1.4}/src/virtualshell/shell.py +11 -1
  16. virtualshell-1.1.4/src/virtualshell/win_pwsh.dll +0 -0
  17. virtualshell-1.1.4/src/virtualshell/zero_copy_bridge.ps1 +584 -0
  18. virtualshell-1.1.4/src/virtualshell/zero_copy_bridge_shell.py +1013 -0
  19. virtualshell-1.1.4/wiki/Usage/Zero-Copy Bridge.md +644 -0
  20. {virtualshell-1.1.2 → virtualshell-1.1.4}/wiki/Usage/make_proxy.md +3 -4
  21. virtualshell-1.1.4/win_pwsh_dll/CMakeLists.txt +79 -0
  22. virtualshell-1.1.4/win_pwsh_dll/include/vs_shm.h +237 -0
  23. virtualshell-1.1.4/win_pwsh_dll/src/object_serializer.cpp +213 -0
  24. virtualshell-1.1.4/win_pwsh_dll/src/vs_shm.cpp +514 -0
  25. virtualshell-1.1.2/src/virtualshell/_version.py +0 -1
  26. {virtualshell-1.1.2 → virtualshell-1.1.4}/.github/workflows/workflow.yml +0 -0
  27. {virtualshell-1.1.2 → virtualshell-1.1.4}/LICENSE +0 -0
  28. {virtualshell-1.1.2 → virtualshell-1.1.4}/bench/bench.csv +0 -0
  29. {virtualshell-1.1.2 → virtualshell-1.1.4}/bench/bench.json +0 -0
  30. {virtualshell-1.1.2 → virtualshell-1.1.4}/bench/vs_bench.py +0 -0
  31. {virtualshell-1.1.2 → virtualshell-1.1.4}/cpp/include/cmd_state.hpp +0 -0
  32. {virtualshell-1.1.2 → virtualshell-1.1.4}/cpp/include/config.hpp +0 -0
  33. {virtualshell-1.1.2 → virtualshell-1.1.4}/cpp/include/dev_debug.hpp +0 -0
  34. {virtualshell-1.1.2 → virtualshell-1.1.4}/cpp/include/execution_result.hpp +0 -0
  35. {virtualshell-1.1.2 → virtualshell-1.1.4}/cpp/include/helpers.hpp +0 -0
  36. {virtualshell-1.1.2 → virtualshell-1.1.4}/cpp/include/io_pump.hpp +0 -0
  37. {virtualshell-1.1.2 → virtualshell-1.1.4}/cpp/include/powershell_process.hpp +0 -0
  38. {virtualshell-1.1.2 → virtualshell-1.1.4}/cpp/include/process.hpp +0 -0
  39. {virtualshell-1.1.2 → virtualshell-1.1.4}/cpp/include/timeout_watcher.hpp +0 -0
  40. {virtualshell-1.1.2 → virtualshell-1.1.4}/cpp/src/io_pump.cpp +0 -0
  41. {virtualshell-1.1.2 → virtualshell-1.1.4}/src/virtualshell/errors.py +0 -0
  42. {virtualshell-1.1.2 → virtualshell-1.1.4}/src/virtualshell/generate_psobject.py +0 -0
  43. {virtualshell-1.1.2 → virtualshell-1.1.4}/src/virtualshell/get-session.ps1 +0 -0
  44. {virtualshell-1.1.2 → virtualshell-1.1.4}/src/virtualshell/save-session.ps1 +0 -0
  45. {virtualshell-1.1.2 → virtualshell-1.1.4}/wiki/Getting started/Getting started.md +0 -0
  46. {virtualshell-1.1.2 → virtualshell-1.1.4}/wiki/Getting started/Installation.md +0 -0
  47. {virtualshell-1.1.2 → virtualshell-1.1.4}/wiki/Getting started/Quickstart.md +0 -0
  48. {virtualshell-1.1.2 → virtualshell-1.1.4}/wiki/Help/FAQ.md +0 -0
  49. {virtualshell-1.1.2 → virtualshell-1.1.4}/wiki/Help/Troubleshooting.md +0 -0
  50. {virtualshell-1.1.2 → virtualshell-1.1.4}/wiki/Home.md +0 -0
  51. {virtualshell-1.1.2 → virtualshell-1.1.4}/wiki/Project/Benchmarks.md +0 -0
  52. {virtualshell-1.1.2 → virtualshell-1.1.4}/wiki/Project/Changelog.md +0 -0
  53. {virtualshell-1.1.2 → virtualshell-1.1.4}/wiki/Project/Design & Architecture.md +0 -0
  54. {virtualshell-1.1.2 → virtualshell-1.1.4}/wiki/Usage/API Overview.md +0 -0
  55. {virtualshell-1.1.2 → virtualshell-1.1.4}/wiki/Usage/Asynchronous Execution.md +0 -0
  56. {virtualshell-1.1.2 → virtualshell-1.1.4}/wiki/Usage/Configuration.md +0 -0
  57. {virtualshell-1.1.2 → virtualshell-1.1.4}/wiki/Usage/Error Handling.md +0 -0
  58. {virtualshell-1.1.2 → virtualshell-1.1.4}/wiki/Usage/Performance Tips.md +0 -0
  59. {virtualshell-1.1.2 → virtualshell-1.1.4}/wiki/Usage/Running Scripts.md +0 -0
  60. {virtualshell-1.1.2 → virtualshell-1.1.4}/wiki/Usage/Security Notes.md +0 -0
  61. {virtualshell-1.1.2 → virtualshell-1.1.4}/wiki/Usage/Synchronous Execution.md +0 -0
  62. {virtualshell-1.1.2 → virtualshell-1.1.4}/wiki/Usage/generate_psobject.md +0 -0
@@ -8,4 +8,5 @@ test/
8
8
  dist/
9
9
  .venv*
10
10
  test_run*
11
- test_sync_timeout.py
11
+ test_performance_bridge.py
12
+ ZEROCOPY_BRIDGE.md
@@ -73,3 +73,4 @@ install(TARGETS _core
73
73
  ARCHIVE DESTINATION virtualshell
74
74
  )
75
75
 
76
+
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: virtualshell
3
- Version: 1.1.2
3
+ Version: 1.1.4
4
4
  Summary: High-performance PowerShell bridge (C++ pybind11 backend)
5
5
  Keywords: powershell,automation,shell,cpp,pybind11
6
6
  Author: Kim-Andre Myrvold
@@ -302,9 +302,70 @@ Every API surface (sync/async/script) accepts `timeout` overrides and optional e
302
302
 
303
303
  ---
304
304
 
305
+ ## Zero-Copy Bridge (Windows only)
306
+
307
+ For high-throughput data transfer between Python and PowerShell, the Zero-Copy Bridge uses shared memory to eliminate serialization overhead. Ideal for large binary data, files, or high-frequency transfers.
308
+
309
+ ```python
310
+ from virtualshell import Shell, ZeroCopyBridge, PSObject
311
+
312
+ with Shell(timeout_seconds=60) as shell:
313
+ with ZeroCopyBridge(shell) as bridge:
314
+ # Python → PowerShell
315
+ data = b"Large binary data" * 100000
316
+ bridge.send(data, "$myData", timeout=30.0)
317
+ res = shell.run("$myData.Length")
318
+ print(f"PowerShell received {res.out.strip()} bytes")
319
+
320
+ # PowerShell → Python
321
+ shell.run("$response = [byte[]]::new(1048576)")
322
+ data = bridge.receive("$response", timeout=30.0)
323
+ print(f"Python received {len(data)} bytes")
324
+ ```
325
+
326
+ For more complex scenarios involving PowerShell objects, you can serialize/deserialize them using the built-in methods:
327
+
328
+ ```python
329
+ with Shell(timeout_seconds=60) as shell:
330
+ # Get processes from PowerShell
331
+ shell.run("$processes = Get-Process | Select-Object -First 5 -Property Name, Id, WorkingSet64")
332
+
333
+ with ZeroCopyBridge(shell) as bridge:
334
+ # Send the process list to Python
335
+ bridge.serialize("processes", out_var="bytes")
336
+ ps_bytes_raw: bytes = bridge.receive("bytes")
337
+
338
+ # Convert bytes to PSObject in Python
339
+ ps_obj = PSObject.from_bytes(ps_bytes_raw)
340
+
341
+ # Print each process info
342
+ for process in ps_obj["Items"]:
343
+ print(f"Process Name: {process['Name']}, ID: {process['Id']}, Memory: {process['WorkingSet64']} bytes")
344
+
345
+ # Modify the process names in Python
346
+ for process in ps_obj["Items"]:
347
+ process["Name"] = f"Modified_{process['Name']}"
348
+
349
+ # Send modified object back to PowerShell
350
+ bridge.send(ps_obj.to_bytes(), "processes")
351
+ bridge.deserialize("processes")
352
+
353
+ # Verify changes in PowerShell
354
+ res = shell.run("$processes | Where-Object { $_.Name -like 'Modified_*' }")
355
+ print("\nModified Processes in PowerShell:")
356
+ print(res.out)
357
+
358
+ ```
359
+
360
+ **Performance:** 5-150 MB/s depending on data size. Requires `win_pwsh.dll` (Windows only).
361
+
362
+ See [Zero-Copy Bridge guide](wiki/Usage/Zero-Copy%20Bridge.md) for complete documentation.
363
+
364
+ ---
365
+
305
366
  ## PowerShell object proxies
306
367
 
307
- `Shell.generate_psobject` reflects a PowerShell object into a Python `Protocol`, while `Shell.make_proxy` creates a live proxy that forwards attribute access back into PowerShell. Together they give you IDE-friendly, type-hinted automation.
368
+ `Shell.generate_psobject` reflects a PowerShell object into a Python `Protocol`, while `Shell.make_proxy` creates a live proxy that forwards attribute access back into PowerShell. Together they give you IDE-friendly, type-hinted automation. This is still an experimental feature and may not cover all edge cases.
308
369
 
309
370
  ```python
310
371
  from virtualshell import Shell
@@ -340,6 +401,8 @@ Detailed guides live in the wiki: [generate_psobject](wiki/Usage/generate_psobje
340
401
  | `Shell.script_async(...)` | Async counterpart of `script`. |
341
402
  | `Shell.save_session()` | Persist the current session to an XML snapshot. |
342
403
  | `Shell.pwsh(text)` | Safely echo a literal PowerShell string (auto quoting). |
404
+ | `Shell.make_proxy(type_name, object_expression, *, depth=4)` | Create a live PowerShell object proxy. |
405
+ | `Shell.generate_psobject(type_expression, output_path)` | Generate a Python `Protocol` for a PowerShell object type. |
343
406
 
344
407
  More helpers live in the wiki, including session restore, batching, and diagnostic tips.
345
408
 
@@ -354,18 +417,21 @@ shell = Shell(
354
417
  powershell_path="C:/Program Files/PowerShell/7/pwsh.exe",
355
418
  working_directory="C:/automation",
356
419
  environment={"MY_FLAG": "1"},
420
+ timeout_seconds=10,
421
+ auto_restart_on_timeout=True,
422
+ stdin_buffer_size=64 * 1024,
357
423
  initial_commands=[
358
424
  "$ErrorActionPreference = 'Stop'",
359
425
  "$ProgressPreference = 'SilentlyContinue'",
360
426
  ],
361
- timeout_seconds=10,
362
- auto_restart_on_timeout=True,
427
+ set_UTF8=True,
428
+ strip_results=False,
363
429
  )
364
430
 
365
431
  shell.start()
366
432
  ```
367
433
 
368
- Configuration is applied before the process starts. You can inspect or replace it later with `shell._core.get_config()` or rebuild the shell.
434
+ Configuration is applied before the process starts. You can inspect later with `shell._core.get_config()`.
369
435
 
370
436
  ---
371
437
 
@@ -83,9 +83,70 @@ Every API surface (sync/async/script) accepts `timeout` overrides and optional e
83
83
 
84
84
  ---
85
85
 
86
+ ## Zero-Copy Bridge (Windows only)
87
+
88
+ For high-throughput data transfer between Python and PowerShell, the Zero-Copy Bridge uses shared memory to eliminate serialization overhead. Ideal for large binary data, files, or high-frequency transfers.
89
+
90
+ ```python
91
+ from virtualshell import Shell, ZeroCopyBridge, PSObject
92
+
93
+ with Shell(timeout_seconds=60) as shell:
94
+ with ZeroCopyBridge(shell) as bridge:
95
+ # Python → PowerShell
96
+ data = b"Large binary data" * 100000
97
+ bridge.send(data, "$myData", timeout=30.0)
98
+ res = shell.run("$myData.Length")
99
+ print(f"PowerShell received {res.out.strip()} bytes")
100
+
101
+ # PowerShell → Python
102
+ shell.run("$response = [byte[]]::new(1048576)")
103
+ data = bridge.receive("$response", timeout=30.0)
104
+ print(f"Python received {len(data)} bytes")
105
+ ```
106
+
107
+ For more complex scenarios involving PowerShell objects, you can serialize/deserialize them using the built-in methods:
108
+
109
+ ```python
110
+ with Shell(timeout_seconds=60) as shell:
111
+ # Get processes from PowerShell
112
+ shell.run("$processes = Get-Process | Select-Object -First 5 -Property Name, Id, WorkingSet64")
113
+
114
+ with ZeroCopyBridge(shell) as bridge:
115
+ # Send the process list to Python
116
+ bridge.serialize("processes", out_var="bytes")
117
+ ps_bytes_raw: bytes = bridge.receive("bytes")
118
+
119
+ # Convert bytes to PSObject in Python
120
+ ps_obj = PSObject.from_bytes(ps_bytes_raw)
121
+
122
+ # Print each process info
123
+ for process in ps_obj["Items"]:
124
+ print(f"Process Name: {process['Name']}, ID: {process['Id']}, Memory: {process['WorkingSet64']} bytes")
125
+
126
+ # Modify the process names in Python
127
+ for process in ps_obj["Items"]:
128
+ process["Name"] = f"Modified_{process['Name']}"
129
+
130
+ # Send modified object back to PowerShell
131
+ bridge.send(ps_obj.to_bytes(), "processes")
132
+ bridge.deserialize("processes")
133
+
134
+ # Verify changes in PowerShell
135
+ res = shell.run("$processes | Where-Object { $_.Name -like 'Modified_*' }")
136
+ print("\nModified Processes in PowerShell:")
137
+ print(res.out)
138
+
139
+ ```
140
+
141
+ **Performance:** 5-150 MB/s depending on data size. Requires `win_pwsh.dll` (Windows only).
142
+
143
+ See [Zero-Copy Bridge guide](wiki/Usage/Zero-Copy%20Bridge.md) for complete documentation.
144
+
145
+ ---
146
+
86
147
  ## PowerShell object proxies
87
148
 
88
- `Shell.generate_psobject` reflects a PowerShell object into a Python `Protocol`, while `Shell.make_proxy` creates a live proxy that forwards attribute access back into PowerShell. Together they give you IDE-friendly, type-hinted automation.
149
+ `Shell.generate_psobject` reflects a PowerShell object into a Python `Protocol`, while `Shell.make_proxy` creates a live proxy that forwards attribute access back into PowerShell. Together they give you IDE-friendly, type-hinted automation. This is still an experimental feature and may not cover all edge cases.
89
150
 
90
151
  ```python
91
152
  from virtualshell import Shell
@@ -121,6 +182,8 @@ Detailed guides live in the wiki: [generate_psobject](wiki/Usage/generate_psobje
121
182
  | `Shell.script_async(...)` | Async counterpart of `script`. |
122
183
  | `Shell.save_session()` | Persist the current session to an XML snapshot. |
123
184
  | `Shell.pwsh(text)` | Safely echo a literal PowerShell string (auto quoting). |
185
+ | `Shell.make_proxy(type_name, object_expression, *, depth=4)` | Create a live PowerShell object proxy. |
186
+ | `Shell.generate_psobject(type_expression, output_path)` | Generate a Python `Protocol` for a PowerShell object type. |
124
187
 
125
188
  More helpers live in the wiki, including session restore, batching, and diagnostic tips.
126
189
 
@@ -135,18 +198,21 @@ shell = Shell(
135
198
  powershell_path="C:/Program Files/PowerShell/7/pwsh.exe",
136
199
  working_directory="C:/automation",
137
200
  environment={"MY_FLAG": "1"},
201
+ timeout_seconds=10,
202
+ auto_restart_on_timeout=True,
203
+ stdin_buffer_size=64 * 1024,
138
204
  initial_commands=[
139
205
  "$ErrorActionPreference = 'Stop'",
140
206
  "$ProgressPreference = 'SilentlyContinue'",
141
207
  ],
142
- timeout_seconds=10,
143
- auto_restart_on_timeout=True,
208
+ set_UTF8=True,
209
+ strip_results=False,
144
210
  )
145
211
 
146
212
  shell.start()
147
213
  ```
148
214
 
149
- Configuration is applied before the process starts. You can inspect or replace it later with `shell._core.get_config()` or rebuild the shell.
215
+ Configuration is applied before the process starts. You can inspect later with `shell._core.get_config()`.
150
216
 
151
217
  ---
152
218
 
@@ -245,7 +245,6 @@ inline py::object make_py_runtime_error(const std::string& msg) {
245
245
  return builtins.attr("RuntimeError")(py::str(msg));
246
246
  }
247
247
 
248
- // ---- replace your make_py_future_from_std_future<T> with this version ----
249
248
  template <class T>
250
249
  py::object make_py_future_from_std_future(std::future<T> fut, py::object py_callback /* may be None */) {
251
250
  // Create the Python Future now (under GIL).
@@ -21,8 +21,25 @@ public:
21
21
  pybind11::dict schema() const;
22
22
 
23
23
  const std::string& type_name() const noexcept { return typeName_; }
24
- struct MethodMeta { bool awaitable{false}; };
25
- struct PropertyMeta { bool writable{false}; };
24
+
25
+ struct ParamMeta {
26
+ std::string name;
27
+ std::string typeName;
28
+ bool isOut{false};
29
+ bool isByRef{false};
30
+ bool isArray{false};
31
+ };
32
+
33
+ struct MethodMeta {
34
+ bool awaitable{false};
35
+ bool returnsVoid{false};
36
+ std::string returnType;
37
+ std::vector<ParamMeta> params;
38
+ };
39
+
40
+ struct PropertyMeta {
41
+ bool writable{false};
42
+ };
26
43
  struct SchemaRecord {
27
44
  std::unordered_map<std::string, MethodMeta> methods;
28
45
  std::unordered_map<std::string, PropertyMeta> properties;
@@ -93,6 +93,8 @@ private:
93
93
  std::deque<uint64_t> inflightOrder_; ///< FIFO order of in-flight command IDs
94
94
  std::atomic<uint32_t> pendingTimeoutSentinels_{0}; ///< Expected stderr timeout sentinels to discard
95
95
 
96
+ std::atomic<int64_t> pid_{-1}; ///< Process ID of the PowerShell host
97
+
96
98
  /**
97
99
  * @internal
98
100
  * @brief Remove and return the CmdState for a given ID.
@@ -150,6 +152,10 @@ public:
150
152
  return shared_from_this();
151
153
  }
152
154
 
155
+ int64_t getProcessId() const {
156
+ return pid_.load(std::memory_order_acquire);
157
+ }
158
+
153
159
  /**
154
160
  * @brief Construct a new VirtualShell with the given configuration.
155
161
  *
@@ -232,6 +232,8 @@ PYBIND11_MODULE(_core, m) {
232
232
  .def("get_config", &VirtualShell::getConfig, py::return_value_policy::reference_internal)
233
233
  .def("update_config",&VirtualShell::updateConfig, py::arg("config"))
234
234
 
235
+ .def("get_process_id", &VirtualShell::getProcessId)
236
+
235
237
  .def("is_restarting", &VirtualShell::isRestarting)
236
238
  .def("get_shared_ptr", &VirtualShell::getSharedPtr)
237
239
  .def("make_proxy",
@@ -274,7 +276,7 @@ PYBIND11_MODULE(_core, m) {
274
276
  }, "Create a new VirtualShell instance", py::arg("config"));
275
277
 
276
278
  // Metadata
277
- m.attr("__version__") = "1.1.2";
279
+ m.attr("__version__") = "1.1.4";
278
280
  m.attr("__author__") = "Kim-Andre Myrvold";
279
281
  }
280
282
 
@@ -25,7 +25,7 @@ using namespace std::chrono_literals;
25
25
  namespace virtualshell {
26
26
  namespace core {
27
27
  namespace {
28
- constexpr std::size_t kReadBufferSize = 64 * 1024;
28
+ constexpr std::size_t kReadBufferSize = 64 * 1024 * 10; // 640 KB read buffer size
29
29
 
30
30
  std::string quote_argument(const std::string& value) {
31
31
  if (value.empty()) {