pyisolate 0.9.1__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.
- pyisolate-0.9.1/LICENSE +21 -0
- pyisolate-0.9.1/MANIFEST.in +8 -0
- pyisolate-0.9.1/PKG-INFO +559 -0
- pyisolate-0.9.1/README.md +502 -0
- pyisolate-0.9.1/pyisolate/__init__.py +75 -0
- pyisolate-0.9.1/pyisolate/_internal/__init__.py +5 -0
- pyisolate-0.9.1/pyisolate/_internal/adapter_registry.py +45 -0
- pyisolate-0.9.1/pyisolate/_internal/bootstrap.py +145 -0
- pyisolate-0.9.1/pyisolate/_internal/client.py +160 -0
- pyisolate-0.9.1/pyisolate/_internal/environment.py +406 -0
- pyisolate-0.9.1/pyisolate/_internal/host.py +494 -0
- pyisolate-0.9.1/pyisolate/_internal/model_serialization.py +158 -0
- pyisolate-0.9.1/pyisolate/_internal/remote_handle.py +31 -0
- pyisolate-0.9.1/pyisolate/_internal/rpc_protocol.py +692 -0
- pyisolate-0.9.1/pyisolate/_internal/rpc_serialization.py +346 -0
- pyisolate-0.9.1/pyisolate/_internal/rpc_transports.py +466 -0
- pyisolate-0.9.1/pyisolate/_internal/sandbox.py +350 -0
- pyisolate-0.9.1/pyisolate/_internal/sandbox_detect.py +326 -0
- pyisolate-0.9.1/pyisolate/_internal/serialization_registry.py +63 -0
- pyisolate-0.9.1/pyisolate/_internal/singleton_context.py +53 -0
- pyisolate-0.9.1/pyisolate/_internal/socket_utils.py +43 -0
- pyisolate-0.9.1/pyisolate/_internal/tensor_serializer.py +503 -0
- pyisolate-0.9.1/pyisolate/_internal/torch_gate.py +26 -0
- pyisolate-0.9.1/pyisolate/_internal/torch_utils.py +51 -0
- pyisolate-0.9.1/pyisolate/_internal/uds_client.py +249 -0
- pyisolate-0.9.1/pyisolate/config.py +71 -0
- pyisolate-0.9.1/pyisolate/host.py +87 -0
- pyisolate-0.9.1/pyisolate/interfaces.py +87 -0
- pyisolate-0.9.1/pyisolate/path_helpers.py +108 -0
- pyisolate-0.9.1/pyisolate/shared.py +49 -0
- pyisolate-0.9.1/pyisolate.egg-info/PKG-INFO +559 -0
- pyisolate-0.9.1/pyisolate.egg-info/SOURCES.txt +77 -0
- pyisolate-0.9.1/pyisolate.egg-info/dependency_links.txt +1 -0
- pyisolate-0.9.1/pyisolate.egg-info/requires.txt +41 -0
- pyisolate-0.9.1/pyisolate.egg-info/top_level.txt +1 -0
- pyisolate-0.9.1/pyproject.toml +135 -0
- pyisolate-0.9.1/setup.cfg +4 -0
- pyisolate-0.9.1/setup.py +12 -0
- pyisolate-0.9.1/tests/__init__.py +1 -0
- pyisolate-0.9.1/tests/conftest.py +114 -0
- pyisolate-0.9.1/tests/fixtures/__init__.py +5 -0
- pyisolate-0.9.1/tests/fixtures/test_adapter.py +301 -0
- pyisolate-0.9.1/tests/harness/__init__.py +0 -0
- pyisolate-0.9.1/tests/harness/host.py +213 -0
- pyisolate-0.9.1/tests/harness/test_package/__init__.py +100 -0
- pyisolate-0.9.1/tests/integration_v2/conftest.py +14 -0
- pyisolate-0.9.1/tests/integration_v2/debug_rpc.py +26 -0
- pyisolate-0.9.1/tests/integration_v2/test_isolation.py +84 -0
- pyisolate-0.9.1/tests/integration_v2/test_lifecycle.py +43 -0
- pyisolate-0.9.1/tests/integration_v2/test_tensors.py +79 -0
- pyisolate-0.9.1/tests/path_unification/__init__.py +1 -0
- pyisolate-0.9.1/tests/path_unification/test_path_helpers.py +241 -0
- pyisolate-0.9.1/tests/test_adapter_contract.py +239 -0
- pyisolate-0.9.1/tests/test_bootstrap.py +85 -0
- pyisolate-0.9.1/tests/test_bootstrap_additional.py +55 -0
- pyisolate-0.9.1/tests/test_bwrap_command.py +461 -0
- pyisolate-0.9.1/tests/test_client_entrypoint_extra.py +206 -0
- pyisolate-0.9.1/tests/test_config_validation.py +212 -0
- pyisolate-0.9.1/tests/test_extension_lifecycle.py +241 -0
- pyisolate-0.9.1/tests/test_extension_safety.py +57 -0
- pyisolate-0.9.1/tests/test_fail_loud.py +35 -0
- pyisolate-0.9.1/tests/test_host_integration.py +52 -0
- pyisolate-0.9.1/tests/test_host_internal_ext.py +282 -0
- pyisolate-0.9.1/tests/test_host_public.py +116 -0
- pyisolate-0.9.1/tests/test_memory_leaks.py +270 -0
- pyisolate-0.9.1/tests/test_path_helpers_contract.py +123 -0
- pyisolate-0.9.1/tests/test_remote_handle.py +31 -0
- pyisolate-0.9.1/tests/test_rpc_contract.py +183 -0
- pyisolate-0.9.1/tests/test_rpc_message_format.py +204 -0
- pyisolate-0.9.1/tests/test_rpc_shutdown.py +136 -0
- pyisolate-0.9.1/tests/test_sandbox_detect.py +450 -0
- pyisolate-0.9.1/tests/test_security.py +160 -0
- pyisolate-0.9.1/tests/test_serialization_contract.py +239 -0
- pyisolate-0.9.1/tests/test_serialization_registry.py +31 -0
- pyisolate-0.9.1/tests/test_shared_additional.py +138 -0
- pyisolate-0.9.1/tests/test_singleton_lifecycle.py +331 -0
- pyisolate-0.9.1/tests/test_singleton_shared.py +140 -0
- pyisolate-0.9.1/tests/test_torch_optional_contract.py +112 -0
- pyisolate-0.9.1/tests/test_torch_utils_additional.py +26 -0
pyisolate-0.9.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Comfy-Org
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
pyisolate-0.9.1/PKG-INFO
ADDED
|
@@ -0,0 +1,559 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pyisolate
|
|
3
|
+
Version: 0.9.1
|
|
4
|
+
Summary: A Python library for dividing execution across multiple virtual environments
|
|
5
|
+
Author-email: Jacob Segal <jacob.e.segal@gmail.com>
|
|
6
|
+
Maintainer-email: Jacob Segal <jacob.e.segal@gmail.com>
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
Project-URL: Homepage, https://github.com/Comfy-Org/pyisolate
|
|
9
|
+
Project-URL: Bug Reports, https://github.com/Comfy-Org/pyisolate/issues
|
|
10
|
+
Project-URL: Source, https://github.com/Comfy-Org/pyisolate
|
|
11
|
+
Keywords: virtual environment,venv,development
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Requires-Dist: uv>=0.1.0
|
|
22
|
+
Requires-Dist: tomli>=2.0.1; python_version < "3.11"
|
|
23
|
+
Provides-Extra: dev
|
|
24
|
+
Requires-Dist: build>=1.2.2; extra == "dev"
|
|
25
|
+
Requires-Dist: twine>=5.1.1; extra == "dev"
|
|
26
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
27
|
+
Requires-Dist: pytest-cov>=4.0; extra == "dev"
|
|
28
|
+
Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
|
|
29
|
+
Requires-Dist: mypy>=1.0; extra == "dev"
|
|
30
|
+
Requires-Dist: pre-commit>=3.0; extra == "dev"
|
|
31
|
+
Requires-Dist: ruff>=0.11.0; extra == "dev"
|
|
32
|
+
Requires-Dist: pyyaml>=5.4.0; extra == "dev"
|
|
33
|
+
Requires-Dist: importlib-metadata>=4.0.0; extra == "dev"
|
|
34
|
+
Provides-Extra: test
|
|
35
|
+
Requires-Dist: numpy<2.0.0,>=1.26.0; extra == "test"
|
|
36
|
+
Requires-Dist: psutil>=5.9.0; extra == "test"
|
|
37
|
+
Requires-Dist: pytest-asyncio>=0.21.0; extra == "test"
|
|
38
|
+
Requires-Dist: pytest>=7.0; extra == "test"
|
|
39
|
+
Requires-Dist: pyyaml>=5.4.0; extra == "test"
|
|
40
|
+
Requires-Dist: tabulate>=0.9.0; extra == "test"
|
|
41
|
+
Requires-Dist: torch>=2.0.0; extra == "test"
|
|
42
|
+
Provides-Extra: bench
|
|
43
|
+
Requires-Dist: numpy<2.0.0,>=1.26.0; extra == "bench"
|
|
44
|
+
Requires-Dist: nvidia-ml-py3>=7.352.0; extra == "bench"
|
|
45
|
+
Requires-Dist: psutil>=5.9.0; extra == "bench"
|
|
46
|
+
Requires-Dist: pytest-asyncio>=0.21.0; extra == "bench"
|
|
47
|
+
Requires-Dist: pytest>=7.0; extra == "bench"
|
|
48
|
+
Requires-Dist: pyyaml>=5.4.0; extra == "bench"
|
|
49
|
+
Requires-Dist: tabulate>=0.9.0; extra == "bench"
|
|
50
|
+
Requires-Dist: torch>=2.0.0; extra == "bench"
|
|
51
|
+
Provides-Extra: docs
|
|
52
|
+
Requires-Dist: sphinx>=5.0; extra == "docs"
|
|
53
|
+
Requires-Dist: sphinx-rtd-theme>=1.0; extra == "docs"
|
|
54
|
+
Requires-Dist: myst-parser>=2.0; extra == "docs"
|
|
55
|
+
Requires-Dist: sphinx-markdown-builder>=0.5.4; extra == "docs"
|
|
56
|
+
Dynamic: license-file
|
|
57
|
+
|
|
58
|
+
# pyisolate
|
|
59
|
+
|
|
60
|
+
**Run Python extensions in isolated virtual environments with seamless inter-process communication.**
|
|
61
|
+
|
|
62
|
+
> 🚨 **Fail Loud Policy**: pyisolate assumes the rest of ComfyUI core is correct. Missing prerequisites or runtime failures immediately raise descriptive exceptions instead of being silently ignored.
|
|
63
|
+
|
|
64
|
+
pyisolate enables you to run Python extensions with conflicting dependencies in the same application by automatically creating isolated virtual environments for each extension using `uv`. Extensions communicate with the host process through a transparent RPC system, making the isolation invisible to your code while keeping the host environment dependency-free.
|
|
65
|
+
|
|
66
|
+
## Requirements
|
|
67
|
+
|
|
68
|
+
- Python 3.9+
|
|
69
|
+
- The [`uv`](https://github.com/astral-sh/uv) CLI available on your `PATH`
|
|
70
|
+
- `pip`/`venv` for bootstrapping the development environment
|
|
71
|
+
- PyTorch is optional and only required for tensor-sharing features (for example, `share_torch=True`)
|
|
72
|
+
|
|
73
|
+
If you want tensor-sharing features, install PyTorch separately (for example: `pip install torch`).
|
|
74
|
+
|
|
75
|
+
## Environment Variables
|
|
76
|
+
|
|
77
|
+
PyIsolate uses several environment variables for configuration and debugging:
|
|
78
|
+
|
|
79
|
+
### Core Variables (Set by PyIsolate automatically)
|
|
80
|
+
- **`PYISOLATE_CHILD`**: Set to `"1"` in isolated child processes. Used to detect if code is running in host or child.
|
|
81
|
+
- **`PYISOLATE_HOST_SNAPSHOT`**: Path to JSON file containing the host's `sys.path` and environment variables. Used during child process initialization.
|
|
82
|
+
- **`PYISOLATE_MODULE_PATH`**: Path to the extension module being loaded. Used to detect ComfyUI root directory.
|
|
83
|
+
|
|
84
|
+
### Debug Variables (Set by user)
|
|
85
|
+
- **`PYISOLATE_PATH_DEBUG`**: Set to `"1"` to enable detailed sys.path logging during child process initialization. Useful for debugging import issues.
|
|
86
|
+
|
|
87
|
+
Example usage:
|
|
88
|
+
```bash
|
|
89
|
+
# Enable detailed path logging
|
|
90
|
+
export PYISOLATE_PATH_DEBUG=1
|
|
91
|
+
python main.py
|
|
92
|
+
|
|
93
|
+
# Disable path logging (default)
|
|
94
|
+
unset PYISOLATE_PATH_DEBUG
|
|
95
|
+
python main.py
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Quick Start
|
|
99
|
+
|
|
100
|
+
### Option A – run everything for me
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
cd /path/to/pyisolate
|
|
104
|
+
./quickstart.sh
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
The script installs `uv`, creates the dev venv, installs pyisolate in editable mode, runs the multi-extension example, and executes the Comfy Hello World demo.
|
|
108
|
+
|
|
109
|
+
### Option B – manual setup (5 minutes)
|
|
110
|
+
|
|
111
|
+
1. **Create the dev environment**
|
|
112
|
+
```bash
|
|
113
|
+
cd /path/to/pyisolate
|
|
114
|
+
uv venv
|
|
115
|
+
source .venv/bin/activate # Windows: .venv\\Scripts\\activate
|
|
116
|
+
uv pip install -e ".[dev]"
|
|
117
|
+
```
|
|
118
|
+
2. **Run the example extensions**
|
|
119
|
+
```bash
|
|
120
|
+
cd example
|
|
121
|
+
python main.py
|
|
122
|
+
cd ..
|
|
123
|
+
```
|
|
124
|
+
Expected output:
|
|
125
|
+
```
|
|
126
|
+
Extension1 | ✓ PASSED | Data processing with pandas/numpy 1.x
|
|
127
|
+
Extension2 | ✓ PASSED | Array processing with numpy 2.x
|
|
128
|
+
Extension3 | ✓ PASSED | HTML parsing with BeautifulSoup/scipy
|
|
129
|
+
```
|
|
130
|
+
3. **Run the Comfy Hello World**
|
|
131
|
+
```bash
|
|
132
|
+
cd comfy_hello_world
|
|
133
|
+
python main.py
|
|
134
|
+
```
|
|
135
|
+
You should see the isolated custom node load, execute, and fetch data from the shared singleton service.
|
|
136
|
+
|
|
137
|
+
## Documentation
|
|
138
|
+
|
|
139
|
+
- Project site: https://comfy-org.github.io/pyisolate/
|
|
140
|
+
- Walkthroughs & architecture notes: see `mysolate/HELLO_WORLD.md` and `mysolate/GETTING_STARTED.md`
|
|
141
|
+
|
|
142
|
+
## Key Benefits
|
|
143
|
+
|
|
144
|
+
- 🔒 **Dependency Isolation**: Run extensions with incompatible dependencies (e.g., numpy 1.x and 2.x) in the same application
|
|
145
|
+
- 🚀 **Zero-Copy PyTorch Tensor Sharing**: Share PyTorch tensors between processes without serialization overhead
|
|
146
|
+
- 🔄 **Transparent Communication**: Call async methods across process boundaries as if they were local
|
|
147
|
+
- 🎯 **Simple API**: Clean, intuitive interface with minimal boilerplate
|
|
148
|
+
- ⚡ **Fast**: Uses `uv` for blazing-fast virtual environment creation
|
|
149
|
+
|
|
150
|
+
## Installation
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
pip install pyisolate
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
For development:
|
|
157
|
+
```bash
|
|
158
|
+
pip install pyisolate[dev]
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## Quick Start
|
|
162
|
+
|
|
163
|
+
### Basic Usage
|
|
164
|
+
|
|
165
|
+
Create an extension that runs in an isolated environment:
|
|
166
|
+
|
|
167
|
+
```python
|
|
168
|
+
# extensions/my_extension/__init__.py
|
|
169
|
+
from pyisolate import ExtensionBase
|
|
170
|
+
|
|
171
|
+
class MyExtension(ExtensionBase):
|
|
172
|
+
def on_module_loaded(self, module):
|
|
173
|
+
self.module = module
|
|
174
|
+
|
|
175
|
+
async def process_data(self, data):
|
|
176
|
+
# This runs in an isolated process with its own dependencies
|
|
177
|
+
import numpy as np # This could be numpy 2.x
|
|
178
|
+
return np.array(data).mean()
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Load and use the extension from your main application:
|
|
182
|
+
|
|
183
|
+
```python
|
|
184
|
+
# main.py
|
|
185
|
+
import pyisolate
|
|
186
|
+
import asyncio
|
|
187
|
+
|
|
188
|
+
async def main():
|
|
189
|
+
# Configure the extension manager
|
|
190
|
+
config = pyisolate.ExtensionManagerConfig(
|
|
191
|
+
venv_root_path="./venvs"
|
|
192
|
+
)
|
|
193
|
+
manager = pyisolate.ExtensionManager(pyisolate.ExtensionBase, config)
|
|
194
|
+
|
|
195
|
+
# Load an extension with specific dependencies
|
|
196
|
+
extension = manager.load_extension(
|
|
197
|
+
pyisolate.ExtensionConfig(
|
|
198
|
+
name="data_processor",
|
|
199
|
+
module_path="./extensions/my_extension",
|
|
200
|
+
isolated=True,
|
|
201
|
+
dependencies=["numpy>=2.0.0"]
|
|
202
|
+
)
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
# Use the extension
|
|
206
|
+
result = await extension.process_data([1, 2, 3, 4, 5])
|
|
207
|
+
print(f"Mean: {result}") # Mean: 3.0
|
|
208
|
+
|
|
209
|
+
# Cleanup
|
|
210
|
+
await extension.stop()
|
|
211
|
+
|
|
212
|
+
asyncio.run(main())
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### PyTorch Tensor Sharing
|
|
216
|
+
|
|
217
|
+
Share PyTorch tensors between processes without serialization:
|
|
218
|
+
|
|
219
|
+
```python
|
|
220
|
+
# extensions/ml_extension/__init__.py
|
|
221
|
+
from pyisolate import ExtensionBase
|
|
222
|
+
import torch
|
|
223
|
+
|
|
224
|
+
class MLExtension(ExtensionBase):
|
|
225
|
+
async def process_tensor(self, tensor: torch.Tensor):
|
|
226
|
+
# Tensor is shared, not copied!
|
|
227
|
+
return tensor.mean()
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
```python
|
|
231
|
+
# main.py
|
|
232
|
+
extension = manager.load_extension(
|
|
233
|
+
pyisolate.ExtensionConfig(
|
|
234
|
+
name="ml_processor",
|
|
235
|
+
module_path="./extensions/ml_extension",
|
|
236
|
+
share_torch=True # Enable zero-copy tensor sharing
|
|
237
|
+
)
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
# Large tensor is shared, not serialized
|
|
241
|
+
large_tensor = torch.randn(1000, 1000)
|
|
242
|
+
mean = await extension.process_tensor(large_tensor)
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### Shared State with Singletons
|
|
246
|
+
|
|
247
|
+
Share state across all extensions using ProxiedSingleton:
|
|
248
|
+
|
|
249
|
+
```python
|
|
250
|
+
# shared.py
|
|
251
|
+
from pyisolate import ProxiedSingleton
|
|
252
|
+
|
|
253
|
+
class DatabaseAPI(ProxiedSingleton):
|
|
254
|
+
def __init__(self):
|
|
255
|
+
self.data = {}
|
|
256
|
+
|
|
257
|
+
def get(self, key):
|
|
258
|
+
return self.data.get(key)
|
|
259
|
+
|
|
260
|
+
def set(self, key, value):
|
|
261
|
+
self.data[key] = value
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
```python
|
|
265
|
+
# extensions/extension_a/__init__.py
|
|
266
|
+
class ExtensionA(ExtensionBase):
|
|
267
|
+
async def save_result(self, result):
|
|
268
|
+
db = DatabaseAPI() # Returns proxy to host's instance
|
|
269
|
+
await db.set("result", result)
|
|
270
|
+
|
|
271
|
+
# extensions/extension_b/__init__.py
|
|
272
|
+
class ExtensionB(ExtensionBase):
|
|
273
|
+
async def get_result(self):
|
|
274
|
+
db = DatabaseAPI() # Returns proxy to host's instance
|
|
275
|
+
return await db.get("result")
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
### Complete Application Structure
|
|
279
|
+
|
|
280
|
+
A complete pyisolate application requires a special `main.py` entry point to handle virtual environment activation:
|
|
281
|
+
|
|
282
|
+
```python
|
|
283
|
+
# main.py
|
|
284
|
+
if __name__ == "__main__":
|
|
285
|
+
# When running as the main script, import and run your host application
|
|
286
|
+
from host import main
|
|
287
|
+
main()
|
|
288
|
+
else:
|
|
289
|
+
# When imported by extension processes, ensure venv is properly activated
|
|
290
|
+
import os
|
|
291
|
+
import site
|
|
292
|
+
import sys
|
|
293
|
+
|
|
294
|
+
if os.name == "nt": # Windows-specific venv activation
|
|
295
|
+
venv = os.environ.get("VIRTUAL_ENV", "")
|
|
296
|
+
if venv != "":
|
|
297
|
+
sys.path.insert(0, os.path.join(venv, "Lib", "site-packages"))
|
|
298
|
+
site.addsitedir(os.path.join(venv, "Lib", "site-packages"))
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
```python
|
|
302
|
+
# host.py - Your main application logic
|
|
303
|
+
import pyisolate
|
|
304
|
+
import asyncio
|
|
305
|
+
|
|
306
|
+
async def async_main():
|
|
307
|
+
# Create extension manager
|
|
308
|
+
config = pyisolate.ExtensionManagerConfig(
|
|
309
|
+
venv_root_path="./extension-venvs"
|
|
310
|
+
)
|
|
311
|
+
manager = pyisolate.ExtensionManager(ExtensionBase, config)
|
|
312
|
+
|
|
313
|
+
# Load extensions (e.g., from a directory or configuration file)
|
|
314
|
+
extensions = []
|
|
315
|
+
for extension_path in discover_extensions():
|
|
316
|
+
extension_config = pyisolate.ExtensionConfig(
|
|
317
|
+
name=extension_name,
|
|
318
|
+
module_path=extension_path,
|
|
319
|
+
isolated=True,
|
|
320
|
+
dependencies=load_dependencies(extension_path),
|
|
321
|
+
apis=[SharedAPI] # Optional shared singletons
|
|
322
|
+
)
|
|
323
|
+
extension = manager.load_extension(extension_config)
|
|
324
|
+
extensions.append(extension)
|
|
325
|
+
|
|
326
|
+
# Use extensions
|
|
327
|
+
for extension in extensions:
|
|
328
|
+
result = await extension.process()
|
|
329
|
+
print(f"Result: {result}")
|
|
330
|
+
|
|
331
|
+
# Clean shutdown
|
|
332
|
+
for extension in extensions:
|
|
333
|
+
await extension.stop()
|
|
334
|
+
|
|
335
|
+
def main():
|
|
336
|
+
asyncio.run(async_main())
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
This structure ensures that:
|
|
340
|
+
- The host application runs normally when executed directly
|
|
341
|
+
- Extension processes properly activate their virtual environments when spawned
|
|
342
|
+
- Windows-specific path handling is properly managed
|
|
343
|
+
|
|
344
|
+
## Features
|
|
345
|
+
|
|
346
|
+
### Core Features
|
|
347
|
+
- **Automatic Virtual Environment Management**: Creates and manages isolated environments automatically
|
|
348
|
+
- **Bidirectional RPC**: Extensions can call host methods and vice versa
|
|
349
|
+
- **Async/Await Support**: Full support for asynchronous programming
|
|
350
|
+
- **Lifecycle Hooks**: `before_module_loaded()`, `on_module_loaded()`, and `stop()` for setup/teardown
|
|
351
|
+
- **Error Propagation**: Exceptions are properly propagated across process boundaries
|
|
352
|
+
|
|
353
|
+
### Advanced Features
|
|
354
|
+
- **Dependency Resolution**: Automatically installs extension-specific dependencies
|
|
355
|
+
- **Platform Support**: Works on Windows, Linux, and soon to be tested on macOS
|
|
356
|
+
- **Context Tracking**: Ensures callbacks happen on the same asyncio loop as the original call
|
|
357
|
+
- **Fast Installation**: Uses `uv` for 10-100x faster package installation without every extension having its own copy of libraries
|
|
358
|
+
|
|
359
|
+
## Architecture
|
|
360
|
+
|
|
361
|
+
```
|
|
362
|
+
┌─────────────────────┐ RPC ┌─────────────┐
|
|
363
|
+
│ Host Process │◄────────────►│ Extension A │
|
|
364
|
+
│ │ │ (venv A) │
|
|
365
|
+
│ ┌──────────────┐ │ └─────────────┘
|
|
366
|
+
│ │ Shared │ │ RPC ┌─────────────┐
|
|
367
|
+
│ │ Singletons │ │◄────────────►│ Extension B │
|
|
368
|
+
│ └──────────────┘ │ │ (venv B) │
|
|
369
|
+
└─────────────────────┘ └─────────────┘
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
## Implementing a Host Adapter (IsolationAdapter)
|
|
373
|
+
|
|
374
|
+
When integrating pyisolate with your application (like ComfyUI), you implement the `IsolationAdapter` protocol. This tells pyisolate how to configure isolated processes for your environment.
|
|
375
|
+
|
|
376
|
+
### Reference Implementation
|
|
377
|
+
|
|
378
|
+
The canonical example is in `tests/fixtures/test_adapter.py`:
|
|
379
|
+
|
|
380
|
+
```python
|
|
381
|
+
from pyisolate.interfaces import IsolationAdapter
|
|
382
|
+
from pyisolate._internal.shared import ProxiedSingleton
|
|
383
|
+
|
|
384
|
+
class MockHostAdapter(IsolationAdapter):
|
|
385
|
+
"""Reference adapter showing all protocol methods."""
|
|
386
|
+
|
|
387
|
+
@property
|
|
388
|
+
def identifier(self) -> str:
|
|
389
|
+
"""Return unique adapter identifier (e.g., 'comfyui')."""
|
|
390
|
+
return "myapp"
|
|
391
|
+
|
|
392
|
+
def get_path_config(self, module_path: str) -> dict:
|
|
393
|
+
"""Configure sys.path for isolated extensions.
|
|
394
|
+
|
|
395
|
+
Returns:
|
|
396
|
+
- preferred_root: Your app's root directory
|
|
397
|
+
- additional_paths: Extra paths for imports
|
|
398
|
+
"""
|
|
399
|
+
return {
|
|
400
|
+
"preferred_root": "/path/to/myapp",
|
|
401
|
+
"additional_paths": ["/path/to/myapp/extensions"],
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
def setup_child_environment(self, snapshot: dict) -> None:
|
|
405
|
+
"""Configure child process after sys.path reconstruction."""
|
|
406
|
+
pass # Set up logging, environment, etc.
|
|
407
|
+
|
|
408
|
+
def register_serializers(self, registry) -> None:
|
|
409
|
+
"""Register custom type serializers for RPC transport."""
|
|
410
|
+
registry.register(
|
|
411
|
+
"MyCustomType",
|
|
412
|
+
serializer=lambda obj: {"data": obj.data},
|
|
413
|
+
deserializer=lambda d: MyCustomType(d["data"]),
|
|
414
|
+
)
|
|
415
|
+
|
|
416
|
+
def provide_rpc_services(self) -> list:
|
|
417
|
+
"""Return ProxiedSingleton classes to expose via RPC."""
|
|
418
|
+
return [MyRegistry, MyProgressReporter]
|
|
419
|
+
|
|
420
|
+
def handle_api_registration(self, api, rpc) -> None:
|
|
421
|
+
"""Post-registration hook for API-specific setup."""
|
|
422
|
+
pass
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
### Testing Your Adapter
|
|
426
|
+
|
|
427
|
+
Run the contract tests to verify your adapter implements the protocol correctly:
|
|
428
|
+
|
|
429
|
+
```bash
|
|
430
|
+
# The test suite verifies all protocol methods
|
|
431
|
+
pytest tests/test_adapter_contract.py -v
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
## Roadmap
|
|
435
|
+
|
|
436
|
+
### ✅ Completed
|
|
437
|
+
- [x] Core isolation and RPC system
|
|
438
|
+
- [x] Automatic virtual environment creation
|
|
439
|
+
- [x] Bidirectional communication
|
|
440
|
+
- [x] PyTorch tensor sharing
|
|
441
|
+
- [x] Shared singleton pattern
|
|
442
|
+
- [x] Comprehensive test suite
|
|
443
|
+
- [x] Windows, Linux support
|
|
444
|
+
- [x] Security features (path normalization)
|
|
445
|
+
- [x] Fast installation with `uv`
|
|
446
|
+
- [x] Context tracking for RPC calls
|
|
447
|
+
- [x] Async/await support
|
|
448
|
+
- [x] Performance benchmarking suite
|
|
449
|
+
- [x] Memory usage tracking and benchmarking
|
|
450
|
+
- [x] Network access restrictions
|
|
451
|
+
- [x] Filesystem access sandboxing
|
|
452
|
+
|
|
453
|
+
### 🚧 In Progress
|
|
454
|
+
- [ ] Documentation site
|
|
455
|
+
- [ ] macOS testing
|
|
456
|
+
- [ ] Wrapper for non-async calls between processes
|
|
457
|
+
|
|
458
|
+
### 🔮 Future Plans
|
|
459
|
+
- [ ] CPU/Memory usage limits
|
|
460
|
+
- [ ] Hot-reloading of extensions
|
|
461
|
+
- [ ] Distributed RPC (across machines)
|
|
462
|
+
- [ ] Profiling and debugging tools
|
|
463
|
+
|
|
464
|
+
## Use Cases
|
|
465
|
+
|
|
466
|
+
pyisolate is perfect for:
|
|
467
|
+
|
|
468
|
+
- **Plugin Systems**: When plugins may require conflicting dependencies
|
|
469
|
+
- **ML Pipelines**: Different models requiring different library versions
|
|
470
|
+
- **Microservices in a Box**: Multiple services with different dependencies in one app
|
|
471
|
+
- **Testing**: Running tests with different dependency versions in parallel
|
|
472
|
+
- **Legacy Code Integration**: Wrapping legacy code with specific dependency requirements
|
|
473
|
+
|
|
474
|
+
## Development
|
|
475
|
+
|
|
476
|
+
We welcome contributions!
|
|
477
|
+
|
|
478
|
+
```bash
|
|
479
|
+
# Setup development environment
|
|
480
|
+
uv venv && source .venv/bin/activate
|
|
481
|
+
uv pip install -e ".[dev,test]"
|
|
482
|
+
pre-commit install
|
|
483
|
+
|
|
484
|
+
# Run tests
|
|
485
|
+
pytest
|
|
486
|
+
|
|
487
|
+
# Run linting
|
|
488
|
+
ruff check pyisolate tests
|
|
489
|
+
|
|
490
|
+
# Run benchmarks
|
|
491
|
+
python benchmarks/simple_benchmark.py
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
### Benchmarking
|
|
495
|
+
|
|
496
|
+
pyisolate includes a comprehensive benchmarking suite to measure RPC call overhead:
|
|
497
|
+
|
|
498
|
+
```bash
|
|
499
|
+
# Install benchmark dependencies
|
|
500
|
+
uv pip install -e ".[bench]"
|
|
501
|
+
|
|
502
|
+
# Quick benchmark using existing example extensions
|
|
503
|
+
python benchmarks/simple_benchmark.py
|
|
504
|
+
|
|
505
|
+
# Full benchmark suite with statistical analysis
|
|
506
|
+
python benchmarks/benchmark.py
|
|
507
|
+
|
|
508
|
+
# Quick mode with fewer iterations for faster results
|
|
509
|
+
python benchmarks/benchmark.py --quick
|
|
510
|
+
|
|
511
|
+
# Skip torch benchmarks (if torch not available)
|
|
512
|
+
python benchmarks/benchmark.py --no-torch
|
|
513
|
+
|
|
514
|
+
# Skip GPU benchmarks
|
|
515
|
+
python benchmarks/benchmark.py --no-gpu
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
#### Example Benchmark Output
|
|
519
|
+
|
|
520
|
+
```
|
|
521
|
+
==================================================
|
|
522
|
+
BENCHMARK RESULTS
|
|
523
|
+
==================================================
|
|
524
|
+
Test Mean (ms) Std Dev (ms) Runs
|
|
525
|
+
--------------------------------------------------
|
|
526
|
+
small_int 0.63 0.05 1000
|
|
527
|
+
small_string 0.64 0.06 1000
|
|
528
|
+
medium_string 0.65 0.07 1000
|
|
529
|
+
tiny_tensor 0.79 0.08 1000
|
|
530
|
+
small_tensor 0.80 0.11 1000
|
|
531
|
+
medium_tensor 0.81 0.06 1000
|
|
532
|
+
large_tensor 0.78 0.08 1000
|
|
533
|
+
model_tensor 0.88 0.29 1000
|
|
534
|
+
|
|
535
|
+
Fastest result: 0.63ms
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
The benchmarks measure:
|
|
539
|
+
|
|
540
|
+
1. **Small Data RPC Overhead**: ~0.6ms for basic data types (integers, strings)
|
|
541
|
+
2. **Tensor Overhead**: Minimal overhead (~0.2ms) for sharing tensors up to 6GB via zero-copy shared memory
|
|
542
|
+
3. **Scaling**: Performance remains O(1) regardless of tensor size
|
|
543
|
+
|
|
544
|
+
> ⚠️ **Note for CPU Tensors**: When checking out or running benchmarks with `share_torch=True`, ensuring `TMPDIR=/dev/shm` is recommended to guarantee that shared memory files are visible to sandboxed child processes.
|
|
545
|
+
|
|
546
|
+
|
|
547
|
+
## License
|
|
548
|
+
|
|
549
|
+
pyisolate is licensed under the MIT License. See [LICENSE](LICENSE) for details.
|
|
550
|
+
|
|
551
|
+
## Acknowledgments
|
|
552
|
+
|
|
553
|
+
- Built on Python's `multiprocessing` and `asyncio`
|
|
554
|
+
- Uses [`uv`](https://github.com/astral-sh/uv) for fast package management
|
|
555
|
+
- Inspired by plugin systems like Chrome Extensions and VS Code Extensions
|
|
556
|
+
|
|
557
|
+
---
|
|
558
|
+
|
|
559
|
+
**Star this repo** if you find it useful! ⭐
|