skypilot-nightly 1.0.0.dev20250513__py3-none-any.whl → 1.0.0.dev20250514__py3-none-any.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.
Files changed (35) hide show
  1. sky/__init__.py +2 -2
  2. sky/backends/backend_utils.py +0 -3
  3. sky/backends/cloud_vm_ray_backend.py +22 -10
  4. sky/clouds/gcp.py +24 -8
  5. sky/clouds/service_catalog/data_fetchers/fetch_gcp.py +33 -11
  6. sky/clouds/service_catalog/gcp_catalog.py +7 -1
  7. sky/dashboard/out/404.html +1 -1
  8. sky/dashboard/out/clusters/[cluster]/[job].html +1 -1
  9. sky/dashboard/out/clusters/[cluster].html +1 -1
  10. sky/dashboard/out/clusters.html +1 -1
  11. sky/dashboard/out/index.html +1 -1
  12. sky/dashboard/out/jobs/[job].html +1 -1
  13. sky/dashboard/out/jobs.html +1 -1
  14. sky/global_user_state.py +0 -2
  15. sky/resources.py +4 -0
  16. sky/server/requests/executor.py +22 -114
  17. sky/server/requests/requests.py +0 -15
  18. sky/server/server.py +7 -12
  19. sky/server/uvicorn.py +2 -12
  20. sky/sky_logging.py +2 -40
  21. sky/skylet/log_lib.py +11 -51
  22. sky/templates/nebius-ray.yml.j2 +3 -1
  23. sky/utils/command_runner.py +0 -3
  24. sky/utils/rich_utils.py +37 -81
  25. sky/utils/subprocess_utils.py +2 -8
  26. {skypilot_nightly-1.0.0.dev20250513.dist-info → skypilot_nightly-1.0.0.dev20250514.dist-info}/METADATA +1 -1
  27. {skypilot_nightly-1.0.0.dev20250513.dist-info → skypilot_nightly-1.0.0.dev20250514.dist-info}/RECORD +33 -35
  28. sky/utils/context.py +0 -264
  29. sky/utils/context_utils.py +0 -172
  30. /sky/dashboard/out/_next/static/{2dkponv64SfFShA8Rnw0D → tdxxQrPV6NW90a983oHXe}/_buildManifest.js +0 -0
  31. /sky/dashboard/out/_next/static/{2dkponv64SfFShA8Rnw0D → tdxxQrPV6NW90a983oHXe}/_ssgManifest.js +0 -0
  32. {skypilot_nightly-1.0.0.dev20250513.dist-info → skypilot_nightly-1.0.0.dev20250514.dist-info}/WHEEL +0 -0
  33. {skypilot_nightly-1.0.0.dev20250513.dist-info → skypilot_nightly-1.0.0.dev20250514.dist-info}/entry_points.txt +0 -0
  34. {skypilot_nightly-1.0.0.dev20250513.dist-info → skypilot_nightly-1.0.0.dev20250514.dist-info}/licenses/LICENSE +0 -0
  35. {skypilot_nightly-1.0.0.dev20250513.dist-info → skypilot_nightly-1.0.0.dev20250514.dist-info}/top_level.txt +0 -0
sky/utils/context.py DELETED
@@ -1,264 +0,0 @@
1
- """SkyPilot context for threads and coroutines."""
2
-
3
- import asyncio
4
- from collections.abc import Mapping
5
- from collections.abc import MutableMapping
6
- import contextvars
7
- import os
8
- import pathlib
9
- import subprocess
10
- import sys
11
- from typing import Dict, Optional, TextIO
12
-
13
-
14
- class Context(object):
15
- """SkyPilot typed context vars for threads and coroutines.
16
-
17
- This is a wrapper around `contextvars.ContextVar` that provides a typed
18
- interface for the SkyPilot specific context variables that can be accessed
19
- at any layer of the call stack. ContextVar is coroutine local, an empty
20
- Context will be intialized for each coroutine when it is created.
21
-
22
- Adding a new context variable for a new feature is as simple as:
23
- 1. Add a new instance variable to the Context class.
24
- 2. (Optional) Add new accessor methods if the variable should be protected.
25
-
26
- To propagate the context to a new thread/coroutine, use
27
- `contextvars.copy_context()`.
28
-
29
- Example:
30
- import asyncio
31
- import contextvars
32
- import time
33
- from sky.utils import context
34
-
35
- def sync_task():
36
- while True:
37
- if context.get().is_canceled():
38
- break
39
- time.sleep(1)
40
-
41
- async def fastapi_handler():
42
- # context.initialize() has been called in lifespan
43
- ctx = contextvars.copy_context()
44
- # asyncio.to_thread copies current context implicitly
45
- task = asyncio.to_thread(sync_task)
46
- # Or explicitly:
47
- # loop = asyncio.get_running_loop()
48
- # ctx = contextvars.copy_context()
49
- # task = loop.run_in_executor(None, ctx.run, sync_task)
50
- await asyncio.sleep(1)
51
- context.get().cancel()
52
- await task
53
- """
54
-
55
- def __init__(self):
56
- self._canceled = asyncio.Event()
57
- self._log_file = None
58
- self._log_file_handle = None
59
- self.env_overrides = {}
60
-
61
- def cancel(self):
62
- """Cancel the context."""
63
- self._canceled.set()
64
-
65
- def is_canceled(self):
66
- """Check if the context is canceled."""
67
- return self._canceled.is_set()
68
-
69
- def redirect_log(
70
- self, log_file: Optional[pathlib.Path]) -> Optional[pathlib.Path]:
71
- """Redirect the stdout and stderr of current context to a file.
72
-
73
- Args:
74
- log_file: The log file to redirect to. If None, the stdout and
75
- stderr will be restored to the original streams.
76
-
77
- Returns:
78
- The old log file, or None if the stdout and stderr were not
79
- redirected.
80
- """
81
- original_log_file = self._log_file
82
- original_log_handle = self._log_file_handle
83
- if log_file is None:
84
- self._log_file_handle = None
85
- else:
86
- self._log_file_handle = open(log_file, 'a', encoding='utf-8')
87
- self._log_file = log_file
88
- if original_log_file is not None:
89
- original_log_handle.close()
90
- return original_log_file
91
-
92
- def output_stream(self, fallback: TextIO) -> TextIO:
93
- if self._log_file_handle is None:
94
- return fallback
95
- else:
96
- return self._log_file_handle
97
-
98
- def override_envs(self, envs: Dict[str, str]):
99
- for k, v in envs.items():
100
- self.env_overrides[k] = v
101
-
102
-
103
- _CONTEXT = contextvars.ContextVar('sky_context', default=None)
104
-
105
-
106
- def get() -> Optional[Context]:
107
- """Get the current SkyPilot context.
108
-
109
- If the context is not initialized, get() will return None. This helps
110
- sync code to check whether it runs in a cancellable context and avoid
111
- polling the cancellation event if it is not.
112
- """
113
- return _CONTEXT.get()
114
-
115
-
116
- class ContextualEnviron(MutableMapping):
117
- """Environment variables wrapper with contextual overrides.
118
-
119
- An instance of ContextualEnviron will typically be used to replace
120
- os.environ to make the envron access of current process contextual
121
- aware.
122
-
123
- Behavior of spawning a subprocess:
124
- - The contexual overrides will not be applied to the subprocess by
125
- default.
126
- - When using env=os.environ to pass the environment variables to the
127
- subprocess explicitly. The subprocess will inherit the contextual
128
- environment variables at the time of the spawn, that is, it will not
129
- see the updates to the environment variables after the spawn. Also,
130
- os.environ of the subprocess will not be a ContextualEnviron unless
131
- the subprocess hijacks os.environ explicitly.
132
- - Optionally, context.Popen() can be used to automatically pass
133
- os.environ with overrides to subprocess.
134
-
135
-
136
- Example:
137
- 1. Parent process:
138
- # Hijack os.environ to be a ContextualEnviron
139
- os.environ = ContextualEnviron(os.environ)
140
- ctx = context.get()
141
- ctx.override_envs({'FOO': 'BAR1'})
142
- proc = subprocess.Popen(..., env=os.environ)
143
- # Or use context.Popen instead
144
- # proc = context.Popen(...)
145
- ctx.override_envs({'FOO': 'BAR2'})
146
- 2. Subprocess:
147
- assert os.environ['FOO'] == 'BAR1'
148
- ctx = context.get()
149
- # Override the contextual env var in the subprocess does not take
150
- # effect since the os.environ is not hijacked.
151
- ctx.override_envs({'FOO': 'BAR3'})
152
- assert os.environ['FOO'] == 'BAR1'
153
- """
154
-
155
- def __init__(self, environ):
156
- self._environ = environ
157
-
158
- def __getitem__(self, key):
159
- ctx = get()
160
- if ctx is not None:
161
- if key in ctx.env_overrides:
162
- return ctx.env_overrides[key]
163
- return self._environ[key]
164
-
165
- def __iter__(self):
166
- ctx = get()
167
- if ctx is not None:
168
- for key in ctx.env_overrides:
169
- yield key
170
- for key in self._environ:
171
- # Deduplicate the keys
172
- if key not in ctx.env_overrides:
173
- yield key
174
- else:
175
- return self._environ.__iter__()
176
-
177
- def __len__(self):
178
- return len(dict(self))
179
-
180
- def __setitem__(self, key, value):
181
- return self._environ.__setitem__(key, value)
182
-
183
- def __delitem__(self, key):
184
- return self._environ.__delitem__(key)
185
-
186
- def __repr__(self):
187
- return self._environ.__repr__()
188
-
189
- def copy(self):
190
- copied = self._environ.copy()
191
- ctx = get()
192
- if ctx is not None:
193
- copied.update(ctx.env_overrides)
194
- return copied
195
-
196
- def setdefault(self, key, default=None):
197
- return self._environ.setdefault(key, default)
198
-
199
- def __ior__(self, other):
200
- if not isinstance(other, Mapping):
201
- return NotImplemented
202
- self.update(other)
203
- return self
204
-
205
- def __or__(self, other):
206
- if not isinstance(other, Mapping):
207
- return NotImplemented
208
- new = dict(self)
209
- new.update(other)
210
- return new
211
-
212
- def __ror__(self, other):
213
- if not isinstance(other, Mapping):
214
- return NotImplemented
215
- new = dict(other)
216
- new.update(self)
217
- return new
218
-
219
-
220
- class Popen(subprocess.Popen):
221
-
222
- def __init__(self, *args, **kwargs):
223
- env = kwargs.pop('env', None)
224
- if env is None:
225
- env = os.environ
226
- super().__init__(*args, env=env, **kwargs)
227
-
228
-
229
- def initialize():
230
- """Initialize the current SkyPilot context."""
231
- _CONTEXT.set(Context())
232
-
233
-
234
- class _ContextualStream:
235
- """A base class for streams that are contextually aware.
236
-
237
- This class implements the TextIO interface via __getattr__ to delegate
238
- attribute access to the original or contextual stream.
239
- """
240
- _original_stream: TextIO
241
-
242
- def __init__(self, original_stream: TextIO):
243
- self._original_stream = original_stream
244
-
245
- def __getattr__(self, attr: str):
246
- return getattr(self._active_stream(), attr)
247
-
248
- def _active_stream(self) -> TextIO:
249
- ctx = get()
250
- if ctx is None:
251
- return self._original_stream
252
- return ctx.output_stream(self._original_stream)
253
-
254
-
255
- class Stdout(_ContextualStream):
256
-
257
- def __init__(self):
258
- super().__init__(sys.stdout)
259
-
260
-
261
- class Stderr(_ContextualStream):
262
-
263
- def __init__(self):
264
- super().__init__(sys.stderr)
@@ -1,172 +0,0 @@
1
- """Utilities for SkyPilot context."""
2
- import asyncio
3
- import functools
4
- import io
5
- import multiprocessing
6
- import os
7
- import subprocess
8
- import sys
9
- import typing
10
- from typing import Any, Callable, IO, Optional, Tuple, TypeVar
11
-
12
- from sky import sky_logging
13
- from sky.utils import context
14
- from sky.utils import subprocess_utils
15
-
16
- StreamHandler = Callable[[IO[Any], IO[Any]], str]
17
-
18
-
19
- # TODO(aylei): call hijack_sys_attrs() proactivly in module init at server-side
20
- # once we have context widely adopted.
21
- def hijack_sys_attrs():
22
- """hijack system attributes to be context aware
23
-
24
- This function should be called at the very beginning of the processes
25
- that might use sky.utils.context.
26
- """
27
- # Modify stdout and stderr of unvicorn process to be contextually aware,
28
- # use setattr to bypass the TextIO type check.
29
- setattr(sys, 'stdout', context.Stdout())
30
- setattr(sys, 'stderr', context.Stderr())
31
- # Reload logger to apply latest stdout and stderr.
32
- sky_logging.reload_logger()
33
- # Hijack os.environ with ContextualEnviron to make env variables
34
- # contextually aware.
35
- setattr(os, 'environ', context.ContextualEnviron(os.environ))
36
- # Hijack subprocess.Popen to pass the contextual environ to subprocess
37
- # by default.
38
- setattr(subprocess, 'Popen', context.Popen)
39
-
40
-
41
- def passthrough_stream_handler(in_stream: IO[Any], out_stream: IO[Any]) -> str:
42
- """Passthrough the stream from the process to the output stream"""
43
- wrapped = io.TextIOWrapper(in_stream,
44
- encoding='utf-8',
45
- newline='',
46
- errors='replace',
47
- write_through=True)
48
- while True:
49
- line = wrapped.readline()
50
- if line:
51
- out_stream.write(line)
52
- out_stream.flush()
53
- else:
54
- break
55
- return ''
56
-
57
-
58
- def pipe_and_wait_process(
59
- ctx: context.Context,
60
- proc: subprocess.Popen,
61
- poll_interval: float = 0.5,
62
- cancel_callback: Optional[Callable[[], None]] = None,
63
- stdout_stream_handler: Optional[StreamHandler] = None,
64
- stderr_stream_handler: Optional[StreamHandler] = None
65
- ) -> Tuple[str, str]:
66
- """Wait for the process to finish or cancel it if the context is cancelled.
67
-
68
- Args:
69
- proc: The process to wait for.
70
- poll_interval: The interval to poll the process.
71
- cancel_callback: The callback to call if the context is cancelled.
72
- stdout_stream_handler: An optional handler to handle the stdout stream,
73
- if None, the stdout stream will be passed through.
74
- stderr_stream_handler: An optional handler to handle the stderr stream,
75
- if None, the stderr stream will be passed through.
76
- """
77
-
78
- if stdout_stream_handler is None:
79
- stdout_stream_handler = passthrough_stream_handler
80
- if stderr_stream_handler is None:
81
- stderr_stream_handler = passthrough_stream_handler
82
-
83
- # Threads are lazily created, so no harm if stderr is None
84
- with multiprocessing.pool.ThreadPool(processes=2) as pool:
85
- # Context will be lost in the new thread, capture current output stream
86
- # and pass it to the new thread directly.
87
- stdout_fut = pool.apply_async(
88
- stdout_stream_handler, (proc.stdout, ctx.output_stream(sys.stdout)))
89
- stderr_fut = None
90
- if proc.stderr is not None:
91
- stderr_fut = pool.apply_async(
92
- stderr_stream_handler,
93
- (proc.stderr, ctx.output_stream(sys.stderr)))
94
- try:
95
- wait_process(ctx,
96
- proc,
97
- poll_interval=poll_interval,
98
- cancel_callback=cancel_callback)
99
- finally:
100
- # Wait for the stream handler threads to exit when process is done
101
- # or cancelled
102
- stdout_fut.wait()
103
- if stderr_fut is not None:
104
- stderr_fut.wait()
105
- stdout = stdout_fut.get()
106
- stderr = ''
107
- if stderr_fut is not None:
108
- stderr = stderr_fut.get()
109
- return stdout, stderr
110
-
111
-
112
- def wait_process(ctx: context.Context,
113
- proc: subprocess.Popen,
114
- poll_interval: float = 0.5,
115
- cancel_callback: Optional[Callable[[], None]] = None):
116
- """Wait for the process to finish or cancel it if the context is cancelled.
117
-
118
- Args:
119
- proc: The process to wait for.
120
- poll_interval: The interval to poll the process.
121
- cancel_callback: The callback to call if the context is cancelled.
122
- """
123
- while True:
124
- if ctx.is_canceled():
125
- if cancel_callback is not None:
126
- cancel_callback()
127
- # Kill the process despite the caller's callback, the utility
128
- # function gracefully handles the case where the process is
129
- # already terminated.
130
- subprocess_utils.kill_process_with_grace_period(proc)
131
- raise asyncio.CancelledError()
132
- try:
133
- proc.wait(poll_interval)
134
- except subprocess.TimeoutExpired:
135
- pass
136
- else:
137
- # Process exited
138
- break
139
-
140
-
141
- F = TypeVar('F', bound=Callable[..., Any])
142
-
143
-
144
- def cancellation_guard(func: F) -> F:
145
- """Decorator to make a synchronous function cancellable via context.
146
-
147
- Guards the function execution by checking context.is_canceled() before
148
- executing the function and raises asyncio.CancelledError if the context
149
- is already cancelled.
150
-
151
- This basically mimics the behavior of asyncio, which checks coroutine
152
- cancelled in await call.
153
-
154
- Args:
155
- func: The function to be decorated.
156
-
157
- Returns:
158
- The wrapped function that checks cancellation before execution.
159
-
160
- Raises:
161
- asyncio.CancelledError: If the context is cancelled before execution.
162
- """
163
-
164
- @functools.wraps(func)
165
- def wrapper(*args, **kwargs):
166
- ctx = context.get()
167
- if ctx is not None and ctx.is_canceled():
168
- raise asyncio.CancelledError(
169
- f'Function {func.__name__} cancelled before execution')
170
- return func(*args, **kwargs)
171
-
172
- return typing.cast(F, wrapper)