xoscar 0.7.11__cp312-cp312-win_amd64.whl → 0.7.13__cp312-cp312-win_amd64.whl

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.

Binary file
Binary file
xoscar/collective/uv.dll CHANGED
Binary file
Binary file
Binary file
xoscar/virtualenv/core.py CHANGED
@@ -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
xoscar/virtualenv/uv.py CHANGED
@@ -16,17 +16,24 @@ from __future__ import annotations
16
16
 
17
17
  import logging
18
18
  import os
19
+ import re
19
20
  import shutil
20
21
  import subprocess
21
22
  import sys
22
23
  import sysconfig
24
+ import tempfile
25
+ from importlib.metadata import distributions
23
26
  from pathlib import Path
24
27
  from typing import Optional
25
28
 
29
+ from packaging.requirements import Requirement
30
+ from packaging.version import Version
31
+
26
32
  from .core import VirtualEnvManager
27
33
  from .utils import run_subprocess_with_logger
28
34
 
29
35
  UV_PATH = os.getenv("XOSCAR_UV_PATH")
36
+ SKIP_INSTALLED = bool(int(os.getenv("XOSCAR_VIRTUAL_ENV_SKIP_INSTALLED", "0")))
30
37
  logger = logging.getLogger(__name__)
31
38
 
32
39
 
@@ -46,7 +53,8 @@ class UVVirtualEnvManager(VirtualEnvManager):
46
53
  return True
47
54
  return shutil.which("uv") is not None
48
55
 
49
- def create_env(self, python_path: Path | None = None) -> None:
56
+ @staticmethod
57
+ def _get_uv_path() -> str:
50
58
  if (uv_path := UV_PATH) is None:
51
59
  try:
52
60
  from uv import find_uv_bin
@@ -55,7 +63,46 @@ class UVVirtualEnvManager(VirtualEnvManager):
55
63
  except (ImportError, FileNotFoundError):
56
64
  logger.warning("Fail to find uv bin, use system one")
57
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()
58
104
  cmd = [uv_path, "venv", str(self.env_path), "--system-site-packages"]
105
+
59
106
  if python_path:
60
107
  cmd += ["--python", str(python_path)]
61
108
  elif _is_in_pyinstaller():
@@ -67,6 +114,112 @@ class UVVirtualEnvManager(VirtualEnvManager):
67
114
  logger.info("Creating virtualenv via command: %s", cmd)
68
115
  subprocess.run(cmd, check=True)
69
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
+
70
223
  def install_packages(self, packages: list[str], **kwargs):
71
224
  """
72
225
  Install packages into the virtual environment using uv.
@@ -75,22 +228,36 @@ class UVVirtualEnvManager(VirtualEnvManager):
75
228
  if not packages:
76
229
  return
77
230
 
78
- # extend the ability of pip
79
- # maybe replace #system_torch# to the real version
80
231
  packages = self.process_packages(packages)
81
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
82
260
 
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
261
  if "index_url" in kwargs and kwargs["index_url"]:
95
262
  cmd += ["-i", kwargs["index_url"]]
96
263
  param_and_option = [
@@ -100,12 +267,13 @@ class UVVirtualEnvManager(VirtualEnvManager):
100
267
  ]
101
268
  for param, option in param_and_option:
102
269
  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]
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
+
109
277
  if kwargs.get("no_build_isolation", False):
110
278
  cmd += ["--no-build-isolation"]
111
279
 
@@ -118,8 +286,7 @@ class UVVirtualEnvManager(VirtualEnvManager):
118
286
  self._install_process = process
119
287
  returncode = process.returncode
120
288
 
121
- self._install_process = None # install finished, clear reference
122
-
289
+ self._install_process = None
123
290
  if returncode != 0:
124
291
  raise subprocess.CalledProcessError(returncode, cmd)
125
292
 
@@ -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,5 +1,5 @@
1
1
  xoscar/__init__.py,sha256=lzCXUmkuIjcjkiNQFekysdJR_ZhlbjcfR5ka1bZZNQg,1683
2
- xoscar/_utils.cp312-win_amd64.pyd,sha256=5-EyWj5kLRkrwT06AfVCyMrvD8zKMT4D5G_6LH-pjf0,108544
2
+ xoscar/_utils.cp312-win_amd64.pyd,sha256=Ux5laS9UUZjehIZDtoCvjSc2iV5oYJFYDpFBg_Z55eo,109056
3
3
  xoscar/_utils.pxd,sha256=rlNbTg5lhXA-jCOLksqF4jhUlNn0xw2jx1HxdLa34pc,1193
4
4
  xoscar/_utils.pyx,sha256=TWScgqmJGYzjbWBOShBLkq07ldfYEQ5fw6V4OytX_IA,7626
5
5
  xoscar/_version.py,sha256=bsfCVAo_o9LkiP3AjPsP4SRRqhjuS0t4D1WGJPzbdls,24412
@@ -7,10 +7,10 @@ xoscar/api.py,sha256=QQDHn-_FiDExmOxEk8BUnq4Qrx13VX3shSlEEqPQJo0,15175
7
7
  xoscar/backend.py,sha256=8G5JwjoOT6Q2slb11eXNApxgcmvNQUCdQzkoIMDwLcQ,1917
8
8
  xoscar/batch.py,sha256=Jk5BSpvMFAV9DrRy0a9tgPvIo_dt8cbJReZBL0cnOPc,8128
9
9
  xoscar/constants.py,sha256=GJ1KEOxwnqksc9K_GH42TSWpQECeC6ti3KJmE3PUcTw,810
10
- xoscar/context.cp312-win_amd64.pyd,sha256=aoTGKIh8xTnl7_QraqugL06mKaLUSarRwzzQgNOeMv8,154624
10
+ xoscar/context.cp312-win_amd64.pyd,sha256=TalcLGIfU4vXBOt6HBoqDjsOgxwr9Q-2yD-9d0ur60M,155648
11
11
  xoscar/context.pxd,sha256=6n6IAbmArSRq8EjcsbS6npW8xP1jI0qOoS1fF0oyj-o,746
12
12
  xoscar/context.pyx,sha256=FOJVerGOvxe2USryXEQA0rpaFX_ScxISH6QWKUcahY8,11310
13
- xoscar/core.cp312-win_amd64.pyd,sha256=X0r6YFVVwBREVIeeib3o4BQyoWn79rfAfKtD50vVGH4,314368
13
+ xoscar/core.cp312-win_amd64.pyd,sha256=Mt5Vd5rF0itSPxZ9vrHNgY_vdeisHf6bVYPFWHWKObg,312832
14
14
  xoscar/core.pxd,sha256=9IZP7dYGfnF1I-obIls8R8b6forxDOPbiM3v5nVslLk,1368
15
15
  xoscar/core.pyx,sha256=U6jCZN74MQHi7HkQRaVGm_w5q-FMsw0nnE3aU6533_Q,22756
16
16
  xoscar/debug.py,sha256=hrmxIH6zvTKasQo6PUUgXu5mgEsR0g87Fvpw7CoHipg,5257
@@ -30,7 +30,7 @@ xoscar/backends/allocate_strategy.py,sha256=DzvTlixwzTANURI2mDLHm3vcaugSPDxU6UQZ
30
30
  xoscar/backends/config.py,sha256=86j0g_Xrl8ENPzBWi296yWg9QEcljvdKK-yJbfYTvQ0,5532
31
31
  xoscar/backends/context.py,sha256=qWwksx8JxYcKR-LQA3PvXh4ReuuTTEyDaLbjpxGXcTA,16766
32
32
  xoscar/backends/core.py,sha256=fbekAxys_t1Dv7if-1R6uVvC_yAxNkXLeQ1V1ZSAfC0,11161
33
- xoscar/backends/message.cp312-win_amd64.pyd,sha256=TLnsEaF_CXDuqdj6GR3mI5XBqlrtkb-cXj0NmnU9EsY,254464
33
+ xoscar/backends/message.cp312-win_amd64.pyd,sha256=sJAY7px6pH0-HyRK-2RDSvH6au2BNBDYW9c78js3UIo,249344
34
34
  xoscar/backends/message.pyi,sha256=m1PrSLvj-IbrHuVfQGoPDs6prI-GJV1vQJqZ5WdQcY4,6798
35
35
  xoscar/backends/message.pyx,sha256=lBEjMJv4VyxmeO9lHXJJazOajbFnTLak4PSBcLoPZvU,20331
36
36
  xoscar/backends/pool.py,sha256=bS_m8XBkfQQsCOaLEzM6HkV5e78dPPp1bCH6yjvPEss,62153
@@ -58,8 +58,8 @@ xoscar/collective/common.py,sha256=9c7xq3IOUvfA0I9GnpalUqXZOzmF6IEILv4zL64BYVE,3
58
58
  xoscar/collective/core.py,sha256=191aPxbUgWpjzrqyozndImDAQhZFmqoQdBkHFLDfXN0,24239
59
59
  xoscar/collective/process_group.py,sha256=kTPbrLMJSGhqbiWvTIiz-X3W0rZWd_CFn_zUIlXbOlM,23286
60
60
  xoscar/collective/utils.py,sha256=p3WEVtXvnVhkuO5mRgQBhBRFr1dKHcDKMjrbMyuiyfg,1219
61
- xoscar/collective/uv.dll,sha256=YtJxA-EjziB0MSpz4n7KlbbDQn9p75Qa5d_daS2fyRo,620544
62
- xoscar/collective/xoscar_pygloo.cp312-win_amd64.pyd,sha256=4Y2IeBw3QKIIYdbR6_DW0P82cUwDNXkEazdegVzf0QM,838656
61
+ xoscar/collective/uv.dll,sha256=5_zAEmONrJ3cEEQ0e0mikyC8JVnUwvYAtJ1Md1l_VTQ,620544
62
+ xoscar/collective/xoscar_pygloo.cp312-win_amd64.pyd,sha256=7W0DAyU6pLhUM0zM50xQCA624RCDkctf2FeMrTOarf8,795136
63
63
  xoscar/collective/xoscar_pygloo.pyi,sha256=6KRzElgNBBKWh-VivUw1b5Dolp17MgwA91hQo33EysU,7616
64
64
  xoscar/metrics/__init__.py,sha256=RjXuuYw4I2YYgD8UY2Z5yCZk0Z56xMJ1n40O80Dtxf8,726
65
65
  xoscar/metrics/api.py,sha256=dtJ4QrIqQNXhJedeqOPs4TXKgrRGZFFN50xAd9SCfec,9144
@@ -71,7 +71,7 @@ xoscar/metrics/backends/prometheus/__init__.py,sha256=ZHepfhCDRuK9yz4pAM7bjpWDvS
71
71
  xoscar/metrics/backends/prometheus/prometheus_metric.py,sha256=65hb8O3tmsEJ7jgOrIwl_suj9SE5Tmqcfjuk0urkLvE,2120
72
72
  xoscar/serialization/__init__.py,sha256=tS8C49yrW_geWNEsbgW3phK1q4YN1ojI6CN-vroIFYM,876
73
73
  xoscar/serialization/aio.py,sha256=7YLXgkWpQ3ANy-TZ1qO8Mt4_J3cZFhFh2FEgUgxMT60,4873
74
- xoscar/serialization/core.cp312-win_amd64.pyd,sha256=oXMyqvAPq2drCra37s8bZJWoXp3vicX_1Eme5LsMPTE,271872
74
+ xoscar/serialization/core.cp312-win_amd64.pyd,sha256=cOqguu1bB235Jyr7XLUggvpCfo4xFoRqtwWnxH6fB40,268800
75
75
  xoscar/serialization/core.pxd,sha256=X-47bqBM2Kzw5SkLqICdKD0gU6CpmLsBxC3kfW--wVk,1013
76
76
  xoscar/serialization/core.pyi,sha256=Zof9373qy2lmjenSuMznEiTSCNW6WQB7rmSXrRbjUo0,1931
77
77
  xoscar/serialization/core.pyx,sha256=ZKexLRnRwZXXn2045kR7xfM_szcoPNrDuouQCWtpFp8,30570
@@ -82,10 +82,10 @@ xoscar/serialization/numpy.py,sha256=C6WVx-Sdl2OHBAvVY34DFjAKXlekMbpc2ni6bR8wxYo
82
82
  xoscar/serialization/pyfury.py,sha256=3ucal29Hr7PX9_1SfB2x43FE2xw_C0rLkVv3foL7qwM,1200
83
83
  xoscar/serialization/scipy.py,sha256=9ph-yoRoNiwUZTwQrn35U60VPirWlncXNAg6EXvqMR4,2554
84
84
  xoscar/virtualenv/__init__.py,sha256=rhJ7I6x7aXjKOCzSqsKLwqFJMh4YC2sqchEIJNEfI58,1151
85
- xoscar/virtualenv/core.py,sha256=6u7zcMiAZVOKSBUXBlJOC5pvwbmycn6svJApYMSJDIY,2943
85
+ xoscar/virtualenv/core.py,sha256=5w1UDnzLHKyrFFwqAJFXVxkpXrd5ux8OlxSR3971bMo,3055
86
86
  xoscar/virtualenv/utils.py,sha256=tZYB1kAEym5e8WK9SxhW7VgZzXk6fJMm6MoBA7pY4gM,2523
87
- xoscar/virtualenv/uv.py,sha256=CbOr9556htAvN3Jy-nO2MNIVs53tcEM6ZuYFFyFFbVw,5025
88
- xoscar-0.7.11.dist-info/METADATA,sha256=4Tdt-SpayO9SuOJPK2FqHBSG1V8MFyhs2OksuAV4AIs,9418
89
- xoscar-0.7.11.dist-info/WHEEL,sha256=8UP9x9puWI0P1V_d7K2oMTBqfeLNm21CTzZ_Ptr0NXU,101
90
- xoscar-0.7.11.dist-info/top_level.txt,sha256=vYlqqY4Nys8Thm1hePIuUv8eQePdULVWMmt7lXtX_ZA,21
91
- xoscar-0.7.11.dist-info/RECORD,,
87
+ xoscar/virtualenv/uv.py,sha256=hHWtt6xxNTKNbuHpYXicgXxb2IDBg5Nqtn7Mw4xBEQU,10976
88
+ xoscar-0.7.13.dist-info/METADATA,sha256=A8UIoDqmIfRjJkhiwoE037Ubz9ybHx0U9-16XoApm9s,9418
89
+ xoscar-0.7.13.dist-info/WHEEL,sha256=8UP9x9puWI0P1V_d7K2oMTBqfeLNm21CTzZ_Ptr0NXU,101
90
+ xoscar-0.7.13.dist-info/top_level.txt,sha256=vYlqqY4Nys8Thm1hePIuUv8eQePdULVWMmt7lXtX_ZA,21
91
+ xoscar-0.7.13.dist-info/RECORD,,