xoscar 0.7.11__tar.gz → 0.7.13__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.

Potentially problematic release.


This version of xoscar might be problematic. Click here for more details.

Files changed (93) hide show
  1. {xoscar-0.7.11 → xoscar-0.7.13}/PKG-INFO +1 -1
  2. {xoscar-0.7.11 → xoscar-0.7.13}/xoscar/virtualenv/core.py +7 -1
  3. xoscar-0.7.13/xoscar/virtualenv/uv.py +308 -0
  4. {xoscar-0.7.11 → xoscar-0.7.13}/xoscar.egg-info/PKG-INFO +1 -1
  5. xoscar-0.7.11/xoscar/virtualenv/uv.py +0 -141
  6. {xoscar-0.7.11 → xoscar-0.7.13}/MANIFEST.in +0 -0
  7. {xoscar-0.7.11 → xoscar-0.7.13}/pyproject.toml +0 -0
  8. {xoscar-0.7.11 → xoscar-0.7.13}/setup.cfg +0 -0
  9. {xoscar-0.7.11 → xoscar-0.7.13}/setup.py +0 -0
  10. {xoscar-0.7.11 → xoscar-0.7.13}/versioneer.py +0 -0
  11. {xoscar-0.7.11 → xoscar-0.7.13}/xoscar/__init__.py +0 -0
  12. {xoscar-0.7.11 → xoscar-0.7.13}/xoscar/_utils.pxd +0 -0
  13. {xoscar-0.7.11 → xoscar-0.7.13}/xoscar/_utils.pyx +0 -0
  14. {xoscar-0.7.11 → xoscar-0.7.13}/xoscar/_version.py +0 -0
  15. {xoscar-0.7.11 → xoscar-0.7.13}/xoscar/aio/__init__.py +0 -0
  16. {xoscar-0.7.11 → xoscar-0.7.13}/xoscar/aio/base.py +0 -0
  17. {xoscar-0.7.11 → xoscar-0.7.13}/xoscar/aio/file.py +0 -0
  18. {xoscar-0.7.11 → xoscar-0.7.13}/xoscar/aio/lru.py +0 -0
  19. {xoscar-0.7.11 → xoscar-0.7.13}/xoscar/aio/parallelism.py +0 -0
  20. {xoscar-0.7.11 → xoscar-0.7.13}/xoscar/api.py +0 -0
  21. {xoscar-0.7.11 → xoscar-0.7.13}/xoscar/backend.py +0 -0
  22. {xoscar-0.7.11 → xoscar-0.7.13}/xoscar/backends/__init__.py +0 -0
  23. {xoscar-0.7.11 → xoscar-0.7.13}/xoscar/backends/allocate_strategy.py +0 -0
  24. {xoscar-0.7.11 → xoscar-0.7.13}/xoscar/backends/communication/__init__.py +0 -0
  25. {xoscar-0.7.11 → xoscar-0.7.13}/xoscar/backends/communication/base.py +0 -0
  26. {xoscar-0.7.11 → xoscar-0.7.13}/xoscar/backends/communication/core.py +0 -0
  27. {xoscar-0.7.11 → xoscar-0.7.13}/xoscar/backends/communication/dummy.py +0 -0
  28. {xoscar-0.7.11 → xoscar-0.7.13}/xoscar/backends/communication/errors.py +0 -0
  29. {xoscar-0.7.11 → xoscar-0.7.13}/xoscar/backends/communication/socket.py +0 -0
  30. {xoscar-0.7.11 → xoscar-0.7.13}/xoscar/backends/communication/ucx.py +0 -0
  31. {xoscar-0.7.11 → xoscar-0.7.13}/xoscar/backends/communication/utils.py +0 -0
  32. {xoscar-0.7.11 → xoscar-0.7.13}/xoscar/backends/config.py +0 -0
  33. {xoscar-0.7.11 → xoscar-0.7.13}/xoscar/backends/context.py +0 -0
  34. {xoscar-0.7.11 → xoscar-0.7.13}/xoscar/backends/core.py +0 -0
  35. {xoscar-0.7.11 → xoscar-0.7.13}/xoscar/backends/indigen/__init__.py +0 -0
  36. {xoscar-0.7.11 → xoscar-0.7.13}/xoscar/backends/indigen/__main__.py +0 -0
  37. {xoscar-0.7.11 → xoscar-0.7.13}/xoscar/backends/indigen/backend.py +0 -0
  38. {xoscar-0.7.11 → xoscar-0.7.13}/xoscar/backends/indigen/driver.py +0 -0
  39. {xoscar-0.7.11 → xoscar-0.7.13}/xoscar/backends/indigen/fate_sharing.py +0 -0
  40. {xoscar-0.7.11 → xoscar-0.7.13}/xoscar/backends/indigen/pool.py +0 -0
  41. {xoscar-0.7.11 → xoscar-0.7.13}/xoscar/backends/indigen/shared_memory.py +0 -0
  42. {xoscar-0.7.11 → xoscar-0.7.13}/xoscar/backends/message.pyi +0 -0
  43. {xoscar-0.7.11 → xoscar-0.7.13}/xoscar/backends/message.pyx +0 -0
  44. {xoscar-0.7.11 → xoscar-0.7.13}/xoscar/backends/pool.py +0 -0
  45. {xoscar-0.7.11 → xoscar-0.7.13}/xoscar/backends/router.py +0 -0
  46. {xoscar-0.7.11 → xoscar-0.7.13}/xoscar/backends/test/__init__.py +0 -0
  47. {xoscar-0.7.11 → xoscar-0.7.13}/xoscar/backends/test/backend.py +0 -0
  48. {xoscar-0.7.11 → xoscar-0.7.13}/xoscar/backends/test/pool.py +0 -0
  49. {xoscar-0.7.11 → xoscar-0.7.13}/xoscar/batch.py +0 -0
  50. {xoscar-0.7.11 → xoscar-0.7.13}/xoscar/collective/__init__.py +0 -0
  51. {xoscar-0.7.11 → xoscar-0.7.13}/xoscar/collective/common.py +0 -0
  52. {xoscar-0.7.11 → xoscar-0.7.13}/xoscar/collective/core.py +0 -0
  53. {xoscar-0.7.11 → xoscar-0.7.13}/xoscar/collective/process_group.py +0 -0
  54. {xoscar-0.7.11 → xoscar-0.7.13}/xoscar/collective/utils.py +0 -0
  55. {xoscar-0.7.11 → xoscar-0.7.13}/xoscar/collective/xoscar_pygloo.pyi +0 -0
  56. {xoscar-0.7.11 → xoscar-0.7.13}/xoscar/constants.py +0 -0
  57. {xoscar-0.7.11 → xoscar-0.7.13}/xoscar/context.pxd +0 -0
  58. {xoscar-0.7.11 → xoscar-0.7.13}/xoscar/context.pyx +0 -0
  59. {xoscar-0.7.11 → xoscar-0.7.13}/xoscar/core.pxd +0 -0
  60. {xoscar-0.7.11 → xoscar-0.7.13}/xoscar/core.pyx +0 -0
  61. {xoscar-0.7.11 → xoscar-0.7.13}/xoscar/debug.py +0 -0
  62. {xoscar-0.7.11 → xoscar-0.7.13}/xoscar/driver.py +0 -0
  63. {xoscar-0.7.11 → xoscar-0.7.13}/xoscar/errors.py +0 -0
  64. {xoscar-0.7.11 → xoscar-0.7.13}/xoscar/libcpp.pxd +0 -0
  65. {xoscar-0.7.11 → xoscar-0.7.13}/xoscar/metrics/__init__.py +0 -0
  66. {xoscar-0.7.11 → xoscar-0.7.13}/xoscar/metrics/api.py +0 -0
  67. {xoscar-0.7.11 → xoscar-0.7.13}/xoscar/metrics/backends/__init__.py +0 -0
  68. {xoscar-0.7.11 → xoscar-0.7.13}/xoscar/metrics/backends/console/__init__.py +0 -0
  69. {xoscar-0.7.11 → xoscar-0.7.13}/xoscar/metrics/backends/console/console_metric.py +0 -0
  70. {xoscar-0.7.11 → xoscar-0.7.13}/xoscar/metrics/backends/metric.py +0 -0
  71. {xoscar-0.7.11 → xoscar-0.7.13}/xoscar/metrics/backends/prometheus/__init__.py +0 -0
  72. {xoscar-0.7.11 → xoscar-0.7.13}/xoscar/metrics/backends/prometheus/prometheus_metric.py +0 -0
  73. {xoscar-0.7.11 → xoscar-0.7.13}/xoscar/nvutils.py +0 -0
  74. {xoscar-0.7.11 → xoscar-0.7.13}/xoscar/profiling.py +0 -0
  75. {xoscar-0.7.11 → xoscar-0.7.13}/xoscar/serialization/__init__.py +0 -0
  76. {xoscar-0.7.11 → xoscar-0.7.13}/xoscar/serialization/aio.py +0 -0
  77. {xoscar-0.7.11 → xoscar-0.7.13}/xoscar/serialization/core.pxd +0 -0
  78. {xoscar-0.7.11 → xoscar-0.7.13}/xoscar/serialization/core.pyi +0 -0
  79. {xoscar-0.7.11 → xoscar-0.7.13}/xoscar/serialization/core.pyx +0 -0
  80. {xoscar-0.7.11 → xoscar-0.7.13}/xoscar/serialization/cuda.py +0 -0
  81. {xoscar-0.7.11 → xoscar-0.7.13}/xoscar/serialization/exception.py +0 -0
  82. {xoscar-0.7.11 → xoscar-0.7.13}/xoscar/serialization/mlx.py +0 -0
  83. {xoscar-0.7.11 → xoscar-0.7.13}/xoscar/serialization/numpy.py +0 -0
  84. {xoscar-0.7.11 → xoscar-0.7.13}/xoscar/serialization/pyfury.py +0 -0
  85. {xoscar-0.7.11 → xoscar-0.7.13}/xoscar/serialization/scipy.py +0 -0
  86. {xoscar-0.7.11 → xoscar-0.7.13}/xoscar/utils.py +0 -0
  87. {xoscar-0.7.11 → xoscar-0.7.13}/xoscar/virtualenv/__init__.py +0 -0
  88. {xoscar-0.7.11 → xoscar-0.7.13}/xoscar/virtualenv/utils.py +0 -0
  89. {xoscar-0.7.11 → xoscar-0.7.13}/xoscar.egg-info/SOURCES.txt +0 -0
  90. {xoscar-0.7.11 → xoscar-0.7.13}/xoscar.egg-info/dependency_links.txt +0 -0
  91. {xoscar-0.7.11 → xoscar-0.7.13}/xoscar.egg-info/not-zip-safe +0 -0
  92. {xoscar-0.7.11 → xoscar-0.7.13}/xoscar.egg-info/requires.txt +0 -0
  93. {xoscar-0.7.11 → xoscar-0.7.13}/xoscar.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: xoscar
3
- Version: 0.7.11
3
+ Version: 0.7.13
4
4
  Summary: Python actor framework for heterogeneous computing.
5
5
  Home-page: http://github.com/xorbitsai/xoscar
6
6
  Author: Qin Xuye
@@ -29,7 +29,13 @@ class VirtualEnvManager(ABC):
29
29
  self.env_path = env_path.resolve()
30
30
 
31
31
  @abstractmethod
32
- def create_env(self, python_path: Path | None = None) -> None:
32
+ def exists_env(self) -> bool:
33
+ pass
34
+
35
+ @abstractmethod
36
+ def create_env(
37
+ self, python_path: Path | None = None, exists: str = "ignore"
38
+ ) -> None:
33
39
  pass
34
40
 
35
41
  @abstractmethod
@@ -0,0 +1,308 @@
1
+ # Copyright 2022-2025 XProbe Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from __future__ import annotations
16
+
17
+ import logging
18
+ import os
19
+ import re
20
+ import shutil
21
+ import subprocess
22
+ import sys
23
+ import sysconfig
24
+ import tempfile
25
+ from importlib.metadata import distributions
26
+ from pathlib import Path
27
+ from typing import Optional
28
+
29
+ from packaging.requirements import Requirement
30
+ from packaging.version import Version
31
+
32
+ from .core import VirtualEnvManager
33
+ from .utils import run_subprocess_with_logger
34
+
35
+ UV_PATH = os.getenv("XOSCAR_UV_PATH")
36
+ SKIP_INSTALLED = bool(int(os.getenv("XOSCAR_VIRTUAL_ENV_SKIP_INSTALLED", "0")))
37
+ logger = logging.getLogger(__name__)
38
+
39
+
40
+ def _is_in_pyinstaller():
41
+ return hasattr(sys, "_MEIPASS")
42
+
43
+
44
+ class UVVirtualEnvManager(VirtualEnvManager):
45
+ def __init__(self, env_path: Path):
46
+ super().__init__(env_path)
47
+ self._install_process: Optional[subprocess.Popen] = None
48
+
49
+ @classmethod
50
+ def is_available(cls):
51
+ if UV_PATH is not None:
52
+ # user specified uv, just treat it as existed
53
+ return True
54
+ return shutil.which("uv") is not None
55
+
56
+ @staticmethod
57
+ def _get_uv_path() -> str:
58
+ if (uv_path := UV_PATH) is None:
59
+ try:
60
+ from uv import find_uv_bin
61
+
62
+ uv_path = find_uv_bin()
63
+ except (ImportError, FileNotFoundError):
64
+ logger.warning("Fail to find uv bin, use system one")
65
+ uv_path = "uv"
66
+ return uv_path
67
+
68
+ def exists_env(self) -> bool:
69
+ """Check if virtual environment already exists."""
70
+ return self.env_path.exists() and (self.env_path / "pyvenv.cfg").exists()
71
+
72
+ def create_env(
73
+ self, python_path: Path | None = None, exists: str = "ignore"
74
+ ) -> None:
75
+ """
76
+ Create virtual environment.
77
+
78
+ Args:
79
+ python_path: Path to Python interpreter to use
80
+ exists: How to handle existing environment:
81
+ - "ignore": Skip creation if environment already exists (default)
82
+ - "error": Raise error if environment exists
83
+ - "clear": Remove existing environment and create new one
84
+ """
85
+ if self.exists_env():
86
+ if exists == "error":
87
+ raise FileExistsError(
88
+ f"Virtual environment already exists at {self.env_path}"
89
+ )
90
+ elif exists == "ignore":
91
+ logger.info(
92
+ f"Virtual environment already exists at {self.env_path}, skipping creation"
93
+ )
94
+ return
95
+ elif exists == "clear":
96
+ logger.info(f"Removing existing virtual environment at {self.env_path}")
97
+ self.remove_env()
98
+ else:
99
+ raise ValueError(
100
+ f"Invalid exists option: {exists}. Must be one of: error, clear, ignore"
101
+ )
102
+
103
+ uv_path = self._get_uv_path()
104
+ cmd = [uv_path, "venv", str(self.env_path), "--system-site-packages"]
105
+
106
+ if python_path:
107
+ cmd += ["--python", str(python_path)]
108
+ elif _is_in_pyinstaller():
109
+ # in pyinstaller, uv would find the system python
110
+ # in this case we'd better specify the same python version
111
+ python_version = f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"
112
+ cmd += ["--python", python_version]
113
+
114
+ logger.info("Creating virtualenv via command: %s", cmd)
115
+ subprocess.run(cmd, check=True)
116
+
117
+ def _resolve_install_plan(
118
+ self, specs: list[str], pinned: dict[str, str]
119
+ ) -> list[str]:
120
+ """
121
+ Run uv --dry-run with pinned constraints and return
122
+ a list like ['package==version', ...].
123
+ """
124
+ with tempfile.NamedTemporaryFile("w+", delete=True) as f:
125
+ for name, ver in pinned.items():
126
+ f.write(f"{name}=={ver}\n")
127
+ f.flush() # make sure content is on disk
128
+
129
+ cmd = [
130
+ self._get_uv_path(),
131
+ "pip",
132
+ "install",
133
+ "-p",
134
+ str(self.env_path),
135
+ "--dry-run",
136
+ "--constraint",
137
+ f.name,
138
+ *specs,
139
+ ]
140
+ result = subprocess.run(cmd, check=True, text=True, capture_output=True)
141
+
142
+ # the temp file is automatically deleted here
143
+ deps = [
144
+ f"{m.group(1)}=={m.group(2)}"
145
+ for line in result.stderr.splitlines()
146
+ if (m := re.match(r"^\+ (\S+)==(\S+)$", line.strip()))
147
+ ]
148
+ return deps
149
+
150
+ @staticmethod
151
+ def _split_specs(
152
+ specs: list[str], installed: dict[str, str]
153
+ ) -> tuple[list[str], dict[str, str]]:
154
+ """
155
+ Split the given requirement specs into:
156
+ - to_resolve: specs that need to be passed to the resolver (unsatisfied ones)
157
+ - pinned: already satisfied specs, used for constraint to lock their versions
158
+ """
159
+ to_resolve: list[str] = []
160
+ pinned: dict[str, str] = {}
161
+
162
+ for spec_str in specs:
163
+ req = Requirement(spec_str)
164
+ name = req.name.lower()
165
+ cur_ver = installed.get(name)
166
+
167
+ if cur_ver is None:
168
+ # Package not installed, needs resolution
169
+ to_resolve.append(spec_str)
170
+ continue
171
+
172
+ if not req.specifier:
173
+ # No version constraint, already satisfied
174
+ pinned[name] = cur_ver
175
+ continue
176
+
177
+ try:
178
+ if Version(cur_ver) in req.specifier:
179
+ # Version satisfies the specifier, pin it
180
+ pinned[name] = cur_ver
181
+ else:
182
+ # Version does not satisfy, needs resolution
183
+ to_resolve.append(spec_str)
184
+ except Exception:
185
+ # Parsing error, be conservative and resolve it
186
+ to_resolve.append(spec_str)
187
+
188
+ return to_resolve, pinned
189
+
190
+ def _filter_packages_not_installed(self, packages: list[str]) -> list[str]:
191
+ """
192
+ Filter out packages that are already installed with the same version.
193
+ """
194
+
195
+ # all the installed packages in system site packages
196
+ installed = {
197
+ dist.metadata["Name"].lower(): dist.version
198
+ for dist in distributions()
199
+ if dist.metadata and "Name" in dist.metadata
200
+ }
201
+
202
+ # exclude those packages that satisfied in system site packages
203
+ to_resolve, pinned = self._split_specs(packages, installed)
204
+ if not to_resolve:
205
+ logger.debug("All requirement specifiers satisfied by system packages.")
206
+ return []
207
+
208
+ resolved = self._resolve_install_plan(to_resolve, pinned)
209
+ logger.debug(f"Resolved install list: {resolved}")
210
+ if not resolved:
211
+ # no packages to install
212
+ return []
213
+
214
+ final = []
215
+ for item in resolved:
216
+ name, version = item.split("==")
217
+ key = name.lower()
218
+ if key not in installed or installed[key] != version:
219
+ final.append(item)
220
+ logger.debug(f"Filtered install list: {final}")
221
+ return final
222
+
223
+ def install_packages(self, packages: list[str], **kwargs):
224
+ """
225
+ Install packages into the virtual environment using uv.
226
+ Supports pip-compatible kwargs: index_url, extra_index_url, find_links.
227
+ """
228
+ if not packages:
229
+ return
230
+
231
+ packages = self.process_packages(packages)
232
+ log = kwargs.pop("log", False)
233
+ skip_installed = kwargs.pop("skip_installed", SKIP_INSTALLED)
234
+ uv_path = self._get_uv_path()
235
+
236
+ if skip_installed:
237
+ packages = self._filter_packages_not_installed(packages)
238
+ if not packages:
239
+ logger.info("All required packages are already installed.")
240
+ return
241
+
242
+ cmd = [
243
+ uv_path,
244
+ "pip",
245
+ "install",
246
+ "-p",
247
+ str(self.env_path),
248
+ "--color=always",
249
+ "--no-deps",
250
+ ] + packages
251
+ else:
252
+ cmd = [
253
+ uv_path,
254
+ "pip",
255
+ "install",
256
+ "-p",
257
+ str(self.env_path),
258
+ "--color=always",
259
+ ] + packages
260
+
261
+ if "index_url" in kwargs and kwargs["index_url"]:
262
+ cmd += ["-i", kwargs["index_url"]]
263
+ param_and_option = [
264
+ ("extra_index_url", "--extra-index-url"),
265
+ ("find_links", "-f"),
266
+ ("trusted_host", "--trusted-host"),
267
+ ]
268
+ for param, option in param_and_option:
269
+ if param in kwargs and kwargs[param]:
270
+ val = kwargs[param]
271
+ cmd += (
272
+ [option, val]
273
+ if isinstance(val, str)
274
+ else [opt for v in val for opt in (option, v)]
275
+ )
276
+
277
+ if kwargs.get("no_build_isolation", False):
278
+ cmd += ["--no-build-isolation"]
279
+
280
+ logger.info("Installing packages via command: %s", cmd)
281
+ if not log:
282
+ self._install_process = process = subprocess.Popen(cmd)
283
+ returncode = process.wait()
284
+ else:
285
+ with run_subprocess_with_logger(cmd) as process:
286
+ self._install_process = process
287
+ returncode = process.returncode
288
+
289
+ self._install_process = None
290
+ if returncode != 0:
291
+ raise subprocess.CalledProcessError(returncode, cmd)
292
+
293
+ def cancel_install(self):
294
+ if self._install_process and self._install_process.poll() is None:
295
+ self._install_process.terminate()
296
+ self._install_process.wait()
297
+
298
+ def get_python_path(self) -> str | None:
299
+ if self.env_path.exists():
300
+ return str(self.env_path.joinpath("bin/python"))
301
+ return None
302
+
303
+ def get_lib_path(self) -> str:
304
+ return sysconfig.get_path("purelib", vars={"base": str(self.env_path)})
305
+
306
+ def remove_env(self):
307
+ if self.env_path.exists():
308
+ shutil.rmtree(self.env_path, ignore_errors=True)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: xoscar
3
- Version: 0.7.11
3
+ Version: 0.7.13
4
4
  Summary: Python actor framework for heterogeneous computing.
5
5
  Home-page: http://github.com/xorbitsai/xoscar
6
6
  Author: Qin Xuye
@@ -1,141 +0,0 @@
1
- # Copyright 2022-2025 XProbe Inc.
2
- #
3
- # Licensed under the Apache License, Version 2.0 (the "License");
4
- # you may not use this file except in compliance with the License.
5
- # You may obtain a copy of the License at
6
- #
7
- # http://www.apache.org/licenses/LICENSE-2.0
8
- #
9
- # Unless required by applicable law or agreed to in writing, software
10
- # distributed under the License is distributed on an "AS IS" BASIS,
11
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- # See the License for the specific language governing permissions and
13
- # limitations under the License.
14
-
15
- from __future__ import annotations
16
-
17
- import logging
18
- import os
19
- import shutil
20
- import subprocess
21
- import sys
22
- import sysconfig
23
- from pathlib import Path
24
- from typing import Optional
25
-
26
- from .core import VirtualEnvManager
27
- from .utils import run_subprocess_with_logger
28
-
29
- UV_PATH = os.getenv("XOSCAR_UV_PATH")
30
- logger = logging.getLogger(__name__)
31
-
32
-
33
- def _is_in_pyinstaller():
34
- return hasattr(sys, "_MEIPASS")
35
-
36
-
37
- class UVVirtualEnvManager(VirtualEnvManager):
38
- def __init__(self, env_path: Path):
39
- super().__init__(env_path)
40
- self._install_process: Optional[subprocess.Popen] = None
41
-
42
- @classmethod
43
- def is_available(cls):
44
- if UV_PATH is not None:
45
- # user specified uv, just treat it as existed
46
- return True
47
- return shutil.which("uv") is not None
48
-
49
- def create_env(self, python_path: Path | None = None) -> None:
50
- if (uv_path := UV_PATH) is None:
51
- try:
52
- from uv import find_uv_bin
53
-
54
- uv_path = find_uv_bin()
55
- except (ImportError, FileNotFoundError):
56
- logger.warning("Fail to find uv bin, use system one")
57
- uv_path = "uv"
58
- cmd = [uv_path, "venv", str(self.env_path), "--system-site-packages"]
59
- if python_path:
60
- cmd += ["--python", str(python_path)]
61
- elif _is_in_pyinstaller():
62
- # in pyinstaller, uv would find the system python
63
- # in this case we'd better specify the same python version
64
- python_version = f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"
65
- cmd += ["--python", python_version]
66
-
67
- logger.info("Creating virtualenv via command: %s", cmd)
68
- subprocess.run(cmd, check=True)
69
-
70
- def install_packages(self, packages: list[str], **kwargs):
71
- """
72
- Install packages into the virtual environment using uv.
73
- Supports pip-compatible kwargs: index_url, extra_index_url, find_links.
74
- """
75
- if not packages:
76
- return
77
-
78
- # extend the ability of pip
79
- # maybe replace #system_torch# to the real version
80
- packages = self.process_packages(packages)
81
- log = kwargs.pop("log", False)
82
-
83
- uv_path = UV_PATH or "uv"
84
- cmd = [
85
- uv_path,
86
- "pip",
87
- "install",
88
- "-p",
89
- str(self.env_path),
90
- "--color=always",
91
- ] + packages
92
-
93
- # Handle known pip-related kwargs
94
- if "index_url" in kwargs and kwargs["index_url"]:
95
- cmd += ["-i", kwargs["index_url"]]
96
- param_and_option = [
97
- ("extra_index_url", "--extra-index-url"),
98
- ("find_links", "-f"),
99
- ("trusted_host", "--trusted-host"),
100
- ]
101
- for param, option in param_and_option:
102
- if param in kwargs and kwargs[param]:
103
- param_value = kwargs[param]
104
- if isinstance(param_value, list):
105
- for it in param_value:
106
- cmd += [option, it]
107
- else:
108
- cmd += [option, param_value]
109
- if kwargs.get("no_build_isolation", False):
110
- cmd += ["--no-build-isolation"]
111
-
112
- logger.info("Installing packages via command: %s", cmd)
113
- if not log:
114
- self._install_process = process = subprocess.Popen(cmd)
115
- returncode = process.wait()
116
- else:
117
- with run_subprocess_with_logger(cmd) as process:
118
- self._install_process = process
119
- returncode = process.returncode
120
-
121
- self._install_process = None # install finished, clear reference
122
-
123
- if returncode != 0:
124
- raise subprocess.CalledProcessError(returncode, cmd)
125
-
126
- def cancel_install(self):
127
- if self._install_process and self._install_process.poll() is None:
128
- self._install_process.terminate()
129
- self._install_process.wait()
130
-
131
- def get_python_path(self) -> str | None:
132
- if self.env_path.exists():
133
- return str(self.env_path.joinpath("bin/python"))
134
- return None
135
-
136
- def get_lib_path(self) -> str:
137
- return sysconfig.get_path("purelib", vars={"base": str(self.env_path)})
138
-
139
- def remove_env(self):
140
- if self.env_path.exists():
141
- shutil.rmtree(self.env_path, ignore_errors=True)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes