skypilot-nightly 1.0.0.dev20250903__py3-none-any.whl → 1.0.0.dev20250904__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.

Potentially problematic release.


This version of skypilot-nightly might be problematic. Click here for more details.

Files changed (72) hide show
  1. sky/__init__.py +2 -2
  2. sky/backends/cloud_vm_ray_backend.py +7 -2
  3. sky/client/cli/command.py +18 -1
  4. sky/client/sdk.py +22 -1
  5. sky/clouds/nebius.py +4 -2
  6. sky/dashboard/out/404.html +1 -1
  7. sky/dashboard/out/_next/static/chunks/{1121-ec35954c8cbea535.js → 1121-408ed10b2f9fce17.js} +1 -1
  8. sky/dashboard/out/_next/static/chunks/{7205-88191679e7988c57.js → 1836-37fede578e2da5f8.js} +4 -9
  9. sky/dashboard/out/_next/static/chunks/3015-86cabed5d4669ad0.js +1 -0
  10. sky/dashboard/out/_next/static/chunks/3294.c80326aec9bfed40.js +6 -0
  11. sky/dashboard/out/_next/static/chunks/{3785.d5b86f6ebc88e6e6.js → 3785.4872a2f3aa489880.js} +1 -1
  12. sky/dashboard/out/_next/static/chunks/{4783.c485f48348349f47.js → 5339.3fda4a4010ff4e06.js} +4 -9
  13. sky/dashboard/out/_next/static/chunks/{9946.3b7b43c217ff70ec.js → 649.b9d7f7d10c1b8c53.js} +4 -9
  14. sky/dashboard/out/_next/static/chunks/6856-66e696640347e77b.js +1 -0
  15. sky/dashboard/out/_next/static/chunks/9025.c12318fb6a1a9093.js +6 -0
  16. sky/dashboard/out/_next/static/chunks/9037-1c0101b86582136f.js +6 -0
  17. sky/dashboard/out/_next/static/chunks/pages/clusters/[cluster]/[job]-39c9bd4cdb7e5a57.js +16 -0
  18. sky/dashboard/out/_next/static/chunks/pages/clusters/{[cluster]-a0527109c2fab467.js → [cluster]-0b4b35dc1dfe046c.js} +2 -7
  19. sky/dashboard/out/_next/static/chunks/pages/infra/{[context]-81351f95f3bec08e.js → [context]-6563820e094f68ca.js} +1 -1
  20. sky/dashboard/out/_next/static/chunks/pages/{infra-c320641c2bcbbea6.js → infra-aabba60d57826e0f.js} +1 -1
  21. sky/dashboard/out/_next/static/chunks/pages/jobs-1f70d9faa564804f.js +1 -0
  22. sky/dashboard/out/_next/static/chunks/pages/workspaces/{[name]-de06e613e20bc977.js → [name]-af76bb06dbb3954f.js} +1 -1
  23. sky/dashboard/out/_next/static/chunks/pages/{workspaces-be35b22e2046564c.js → workspaces-7598c33a746cdc91.js} +1 -1
  24. sky/dashboard/out/_next/static/chunks/webpack-24c4fc6d30ce0193.js +1 -0
  25. sky/dashboard/out/_next/static/mriHUOVL_Ht-CeW-e7saa/_buildManifest.js +1 -0
  26. sky/dashboard/out/clusters/[cluster]/[job].html +1 -1
  27. sky/dashboard/out/clusters/[cluster].html +1 -1
  28. sky/dashboard/out/clusters.html +1 -1
  29. sky/dashboard/out/config.html +1 -1
  30. sky/dashboard/out/index.html +1 -1
  31. sky/dashboard/out/infra/[context].html +1 -1
  32. sky/dashboard/out/infra.html +1 -1
  33. sky/dashboard/out/jobs/[job].html +1 -1
  34. sky/dashboard/out/jobs/pools/[pool].html +1 -1
  35. sky/dashboard/out/jobs.html +1 -1
  36. sky/dashboard/out/users.html +1 -1
  37. sky/dashboard/out/volumes.html +1 -1
  38. sky/dashboard/out/workspace/new.html +1 -1
  39. sky/dashboard/out/workspaces/[name].html +1 -1
  40. sky/dashboard/out/workspaces.html +1 -1
  41. sky/data/mounting_utils.py +29 -38
  42. sky/global_user_state.py +17 -5
  43. sky/jobs/state.py +1 -1
  44. sky/provision/kubernetes/instance.py +10 -3
  45. sky/serve/serve_state.py +1 -1
  46. sky/server/config.py +31 -3
  47. sky/server/requests/executor.py +9 -3
  48. sky/server/requests/requests.py +9 -0
  49. sky/server/server.py +24 -21
  50. sky/server/uvicorn.py +9 -3
  51. sky/skylet/constants.py +1 -1
  52. sky/skypilot_config.py +21 -9
  53. sky/ssh_node_pools/server.py +5 -5
  54. sky/users/server.py +18 -15
  55. sky/utils/db/db_utils.py +58 -1
  56. sky/utils/db/migration_utils.py +0 -32
  57. {skypilot_nightly-1.0.0.dev20250903.dist-info → skypilot_nightly-1.0.0.dev20250904.dist-info}/METADATA +33 -33
  58. {skypilot_nightly-1.0.0.dev20250903.dist-info → skypilot_nightly-1.0.0.dev20250904.dist-info}/RECORD +63 -63
  59. sky/dashboard/out/_next/static/chunks/3015-8089ed1e0b7e37fd.js +0 -1
  60. sky/dashboard/out/_next/static/chunks/6856-049014c6d43d127b.js +0 -1
  61. sky/dashboard/out/_next/static/chunks/9025.a1bef12d672bb66d.js +0 -6
  62. sky/dashboard/out/_next/static/chunks/9037-89a84fd7fa31362d.js +0 -6
  63. sky/dashboard/out/_next/static/chunks/9984.7eb6cc51fb460cae.js +0 -6
  64. sky/dashboard/out/_next/static/chunks/pages/clusters/[cluster]/[job]-b77360a343d48902.js +0 -16
  65. sky/dashboard/out/_next/static/chunks/pages/jobs-7421e63ac35f8fce.js +0 -1
  66. sky/dashboard/out/_next/static/chunks/webpack-60556df644cd5d71.js +0 -1
  67. sky/dashboard/out/_next/static/yLz6EPhW_XXmnNs1I6dmS/_buildManifest.js +0 -1
  68. /sky/dashboard/out/_next/static/{yLz6EPhW_XXmnNs1I6dmS → mriHUOVL_Ht-CeW-e7saa}/_ssgManifest.js +0 -0
  69. {skypilot_nightly-1.0.0.dev20250903.dist-info → skypilot_nightly-1.0.0.dev20250904.dist-info}/WHEEL +0 -0
  70. {skypilot_nightly-1.0.0.dev20250903.dist-info → skypilot_nightly-1.0.0.dev20250904.dist-info}/entry_points.txt +0 -0
  71. {skypilot_nightly-1.0.0.dev20250903.dist-info → skypilot_nightly-1.0.0.dev20250904.dist-info}/licenses/LICENSE +0 -0
  72. {skypilot_nightly-1.0.0.dev20250903.dist-info → skypilot_nightly-1.0.0.dev20250904.dist-info}/top_level.txt +0 -0
@@ -1,5 +1,6 @@
1
1
  """Utilities for REST API."""
2
2
  import asyncio
3
+ import atexit
3
4
  import contextlib
4
5
  import dataclasses
5
6
  import enum
@@ -842,3 +843,11 @@ async def requests_gc_daemon():
842
843
  # Run the daemon at most once every hour to avoid too frequent
843
844
  # cleanup.
844
845
  await asyncio.sleep(max(retention_seconds, 3600))
846
+
847
+
848
+ def _cleanup():
849
+ if _DB is not None:
850
+ asyncio.run(_DB.close())
851
+
852
+
853
+ atexit.register(_cleanup)
sky/server/server.py CHANGED
@@ -24,7 +24,6 @@ import aiofiles
24
24
  import anyio
25
25
  import fastapi
26
26
  from fastapi.middleware import cors
27
- from sqlalchemy import pool
28
27
  import starlette.middleware.base
29
28
  import uvloop
30
29
 
@@ -72,6 +71,7 @@ from sky.utils import dag_utils
72
71
  from sky.utils import perf_utils
73
72
  from sky.utils import status_lib
74
73
  from sky.utils import subprocess_utils
74
+ from sky.utils.db import db_utils
75
75
  from sky.volumes.server import server as volumes_rest
76
76
  from sky.workspaces import server as workspaces_rest
77
77
 
@@ -1322,18 +1322,17 @@ async def download(download_body: payloads.DownloadBody,
1322
1322
  detail=f'Error creating zip file: {str(e)}')
1323
1323
 
1324
1324
 
1325
+ # TODO(aylei): run it asynchronously after global_user_state support async op
1325
1326
  @app.post('/provision_logs')
1326
- async def provision_logs(cluster_body: payloads.ClusterNameBody,
1327
- follow: bool = True,
1328
- tail: int = 0) -> fastapi.responses.StreamingResponse:
1327
+ def provision_logs(cluster_body: payloads.ClusterNameBody,
1328
+ follow: bool = True,
1329
+ tail: int = 0) -> fastapi.responses.StreamingResponse:
1329
1330
  """Streams the provision.log for the latest launch request of a cluster."""
1330
1331
  # Prefer clusters table first, then cluster_history as fallback.
1331
- log_path_str = await context_utils.to_thread(
1332
- global_user_state.get_cluster_provision_log_path,
1332
+ log_path_str = global_user_state.get_cluster_provision_log_path(
1333
1333
  cluster_body.cluster_name)
1334
1334
  if not log_path_str:
1335
- log_path_str = await context_utils.to_thread(
1336
- global_user_state.get_cluster_history_provision_log_path,
1335
+ log_path_str = global_user_state.get_cluster_history_provision_log_path(
1337
1336
  cluster_body.cluster_name)
1338
1337
  if not log_path_str:
1339
1338
  raise fastapi.HTTPException(
@@ -1908,13 +1907,6 @@ if __name__ == '__main__':
1908
1907
 
1909
1908
  skyuvicorn.add_timestamp_prefix_for_server_logs()
1910
1909
 
1911
- # Initialize global user state db
1912
- global_user_state.initialize_and_get_db(pool.QueuePool)
1913
- # Initialize request db
1914
- requests_lib.reset_db_and_logs()
1915
- # Restore the server user hash
1916
- _init_or_restore_server_user_hash()
1917
-
1918
1910
  parser = argparse.ArgumentParser()
1919
1911
  parser.add_argument('--host', default='127.0.0.1')
1920
1912
  parser.add_argument('--port', default=46580, type=int)
@@ -1930,7 +1922,17 @@ if __name__ == '__main__':
1930
1922
  # that it is shown only when the API server is started.
1931
1923
  usage_lib.maybe_show_privacy_policy()
1932
1924
 
1933
- config = server_config.compute_server_config(cmd_args.deploy)
1925
+ # Initialize global user state db
1926
+ db_utils.set_max_connections(1)
1927
+ global_user_state.initialize_and_get_db()
1928
+ # Initialize request db
1929
+ requests_lib.reset_db_and_logs()
1930
+ # Restore the server user hash
1931
+ _init_or_restore_server_user_hash()
1932
+ max_db_connections = global_user_state.get_max_db_connections()
1933
+ config = server_config.compute_server_config(cmd_args.deploy,
1934
+ max_db_connections)
1935
+
1934
1936
  num_workers = config.num_server_workers
1935
1937
 
1936
1938
  queue_server: Optional[multiprocessing.Process] = None
@@ -1955,11 +1957,12 @@ if __name__ == '__main__':
1955
1957
  logger.info(f'Starting SkyPilot API server, workers={num_workers}')
1956
1958
  # We don't support reload for now, since it may cause leakage of request
1957
1959
  # workers or interrupt running requests.
1958
- config = uvicorn.Config('sky.server.server:app',
1959
- host=cmd_args.host,
1960
- port=cmd_args.port,
1961
- workers=num_workers)
1962
- skyuvicorn.run(config)
1960
+ uvicorn_config = uvicorn.Config('sky.server.server:app',
1961
+ host=cmd_args.host,
1962
+ port=cmd_args.port,
1963
+ workers=num_workers)
1964
+ skyuvicorn.run(uvicorn_config,
1965
+ max_db_connections=config.num_db_connections_per_worker)
1963
1966
  except Exception as exc: # pylint: disable=broad-except
1964
1967
  logger.error(f'Failed to start SkyPilot API server: '
1965
1968
  f'{common_utils.format_exception(exc, use_bracket=True)}')
sky/server/uvicorn.py CHANGED
@@ -26,6 +26,7 @@ from sky.utils import context_utils
26
26
  from sky.utils import env_options
27
27
  from sky.utils import perf_utils
28
28
  from sky.utils import subprocess_utils
29
+ from sky.utils.db import db_utils
29
30
 
30
31
  logger = sky_logging.init_logger(__name__)
31
32
 
@@ -88,9 +89,12 @@ class Server(uvicorn.Server):
88
89
  - Run the server process with contextually aware.
89
90
  """
90
91
 
91
- def __init__(self, config: uvicorn.Config):
92
+ def __init__(self,
93
+ config: uvicorn.Config,
94
+ max_db_connections: Optional[int] = None):
92
95
  super().__init__(config=config)
93
96
  self.exiting: bool = False
97
+ self.max_db_connections = max_db_connections
94
98
 
95
99
  def handle_exit(self, sig: int, frame: Union[FrameType, None]) -> None:
96
100
  """Handle exit signal.
@@ -196,6 +200,8 @@ class Server(uvicorn.Server):
196
200
 
197
201
  def run(self, *args, **kwargs):
198
202
  """Run the server process."""
203
+ if self.max_db_connections is not None:
204
+ db_utils.set_max_connections(self.max_db_connections)
199
205
  add_timestamp_prefix_for_server_logs()
200
206
  context_utils.hijack_sys_attrs()
201
207
  # Use default loop policy of uvicorn (use uvloop if available).
@@ -210,14 +216,14 @@ class Server(uvicorn.Server):
210
216
  asyncio.run(self.serve(*args, **kwargs))
211
217
 
212
218
 
213
- def run(config: uvicorn.Config):
219
+ def run(config: uvicorn.Config, max_db_connections: Optional[int] = None):
214
220
  """Run unvicorn server."""
215
221
  if config.reload:
216
222
  # Reload and multi-workers are mutually exclusive
217
223
  # in uvicorn. Since we do not use reload now, simply
218
224
  # guard by an exception.
219
225
  raise ValueError('Reload is not supported yet.')
220
- server = Server(config=config)
226
+ server = Server(config=config, max_db_connections=max_db_connections)
221
227
  try:
222
228
  if config.workers is not None and config.workers > 1:
223
229
  sock = config.bind_socket()
sky/skylet/constants.py CHANGED
@@ -362,7 +362,7 @@ SKY_SSH_USER_PLACEHOLDER = 'skypilot:ssh_user'
362
362
 
363
363
  RCLONE_CONFIG_DIR = '~/.config/rclone'
364
364
  RCLONE_CONFIG_PATH = f'{RCLONE_CONFIG_DIR}/rclone.conf'
365
- RCLONE_LOG_DIR = '~/.sky/rclone_log'
365
+ RCLONE_MOUNT_CACHED_LOG_DIR = '~/.sky/rclone_log'
366
366
  RCLONE_CACHE_DIR = '~/.cache/rclone'
367
367
  RCLONE_CACHE_REFRESH_INTERVAL = 10
368
368
 
sky/skypilot_config.py CHANGED
@@ -227,7 +227,7 @@ def _get_config_from_path(path: Optional[str]) -> config_utils.Config:
227
227
  return parse_and_validate_config_file(path)
228
228
 
229
229
 
230
- def _resolve_user_config_path() -> Optional[str]:
230
+ def resolve_user_config_path() -> Optional[str]:
231
231
  # find the user config file path, None if not resolved.
232
232
  user_config_path = _get_config_file_path(ENV_VAR_GLOBAL_CONFIG)
233
233
  if user_config_path:
@@ -252,7 +252,7 @@ def _resolve_user_config_path() -> Optional[str]:
252
252
 
253
253
  def get_user_config() -> config_utils.Config:
254
254
  """Returns the user config."""
255
- return _get_config_from_path(_resolve_user_config_path())
255
+ return _get_config_from_path(resolve_user_config_path())
256
256
 
257
257
 
258
258
  def _resolve_project_config_path() -> Optional[str]:
@@ -574,8 +574,13 @@ def _reload_config_as_server() -> None:
574
574
  'If db config is specified, no other config is allowed')
575
575
  logger.debug('retrieving config from database')
576
576
  with _DB_USE_LOCK:
577
- sqlalchemy_engine = sqlalchemy.create_engine(db_url,
578
- poolclass=NullPool)
577
+ dispose_engine = False
578
+ if db_utils.get_max_connections() == 0:
579
+ dispose_engine = True
580
+ sqlalchemy_engine = sqlalchemy.create_engine(db_url,
581
+ poolclass=NullPool)
582
+ else:
583
+ sqlalchemy_engine = db_utils.get_engine('config')
579
584
  db_utils.add_all_tables_to_db_sqlalchemy(Base.metadata,
580
585
  sqlalchemy_engine)
581
586
 
@@ -597,7 +602,8 @@ def _reload_config_as_server() -> None:
597
602
  server_config = overlay_skypilot_config(server_config,
598
603
  db_config)
599
604
  # Close the engine to avoid connection leaks
600
- sqlalchemy_engine.dispose()
605
+ if dispose_engine:
606
+ sqlalchemy_engine.dispose()
601
607
  if sky_logging.logging_enabled(logger, sky_logging.DEBUG):
602
608
  logger.debug(f'server config: \n'
603
609
  f'{yaml_utils.dump_yaml_str(dict(server_config))}')
@@ -611,7 +617,7 @@ def _reload_config_as_client() -> None:
611
617
  _set_loaded_config_path(None)
612
618
 
613
619
  overrides: List[config_utils.Config] = []
614
- user_config_path = _resolve_user_config_path()
620
+ user_config_path = resolve_user_config_path()
615
621
  user_config = _get_config_from_path(user_config_path)
616
622
  if user_config:
617
623
  overrides.append(user_config)
@@ -867,8 +873,13 @@ def update_api_server_config_no_lock(config: config_utils.Config) -> None:
867
873
  raise ValueError('Cannot change db url while server is running')
868
874
  if existing_db_url:
869
875
  with _DB_USE_LOCK:
870
- sqlalchemy_engine = sqlalchemy.create_engine(existing_db_url,
871
- poolclass=NullPool)
876
+ dispose_engine = False
877
+ if db_utils.get_max_connections() == 0:
878
+ dispose_engine = True
879
+ sqlalchemy_engine = sqlalchemy.create_engine(
880
+ existing_db_url, poolclass=NullPool)
881
+ else:
882
+ sqlalchemy_engine = db_utils.get_engine('config')
872
883
  db_utils.add_all_tables_to_db_sqlalchemy(
873
884
  Base.metadata, sqlalchemy_engine)
874
885
 
@@ -897,7 +908,8 @@ def update_api_server_config_no_lock(config: config_utils.Config) -> None:
897
908
  _set_config_yaml_to_db(API_SERVER_CONFIG_KEY, config)
898
909
  db_updated = True
899
910
  # Close the engine to avoid connection leaks
900
- sqlalchemy_engine.dispose()
911
+ if dispose_engine:
912
+ sqlalchemy_engine.dispose()
901
913
 
902
914
  if not db_updated:
903
915
  # save to the local file (PVC in Kubernetes, local file otherwise)
@@ -15,7 +15,7 @@ router = fastapi.APIRouter()
15
15
 
16
16
 
17
17
  @router.get('')
18
- async def get_ssh_node_pools() -> Dict[str, Any]:
18
+ def get_ssh_node_pools() -> Dict[str, Any]:
19
19
  """Get all SSH Node Pool configurations."""
20
20
  try:
21
21
  return ssh_node_pools_core.get_all_pools()
@@ -27,7 +27,7 @@ async def get_ssh_node_pools() -> Dict[str, Any]:
27
27
 
28
28
 
29
29
  @router.post('')
30
- async def update_ssh_node_pools(pools_config: Dict[str, Any]) -> Dict[str, str]:
30
+ def update_ssh_node_pools(pools_config: Dict[str, Any]) -> Dict[str, str]:
31
31
  """Update SSH Node Pool configurations."""
32
32
  try:
33
33
  ssh_node_pools_core.update_pools(pools_config)
@@ -39,7 +39,7 @@ async def update_ssh_node_pools(pools_config: Dict[str, Any]) -> Dict[str, str]:
39
39
 
40
40
 
41
41
  @router.delete('/{pool_name}')
42
- async def delete_ssh_node_pool(pool_name: str) -> Dict[str, str]:
42
+ def delete_ssh_node_pool(pool_name: str) -> Dict[str, str]:
43
43
  """Delete a SSH Node Pool configuration."""
44
44
  try:
45
45
  if ssh_node_pools_core.delete_pool(pool_name):
@@ -83,7 +83,7 @@ async def upload_ssh_key(request: fastapi.Request) -> Dict[str, str]:
83
83
 
84
84
 
85
85
  @router.get('/keys')
86
- async def list_ssh_keys() -> List[str]:
86
+ def list_ssh_keys() -> List[str]:
87
87
  """List available SSH keys."""
88
88
  try:
89
89
  return ssh_node_pools_core.list_ssh_keys()
@@ -200,7 +200,7 @@ async def down_ssh_node_pool_general(
200
200
 
201
201
 
202
202
  @router.get('/{pool_name}/status')
203
- async def get_ssh_node_pool_status(pool_name: str) -> Dict[str, str]:
203
+ def get_ssh_node_pool_status(pool_name: str) -> Dict[str, str]:
204
204
  """Get the status of a specific SSH Node Pool."""
205
205
  try:
206
206
  # Call ssh_status to check the context
sky/users/server.py CHANGED
@@ -33,8 +33,12 @@ USER_LOCK_TIMEOUT_SECONDS = 20
33
33
  router = fastapi.APIRouter()
34
34
 
35
35
 
36
+ # All handlers in user handler are sync to get fastAPI run it in a
37
+ # ThreadPoolExecutor to avoid blocking the async event loop.
38
+ # TODO(aylei): make these async once we have the global_user_state async
39
+ # support.
36
40
  @router.get('')
37
- async def users() -> List[Dict[str, Any]]:
41
+ def users() -> List[Dict[str, Any]]:
38
42
  """Gets all users."""
39
43
  all_users = []
40
44
  user_list = global_user_state.get_all_users()
@@ -54,7 +58,7 @@ async def users() -> List[Dict[str, Any]]:
54
58
 
55
59
 
56
60
  @router.get('/role')
57
- async def get_current_user_role(request: fastapi.Request):
61
+ def get_current_user_role(request: fastapi.Request):
58
62
  """Get current user's role."""
59
63
  # TODO(hailong): is there a reliable way to get the user
60
64
  # hash for the request without 'X-Auth-Request-Email' header?
@@ -70,7 +74,7 @@ async def get_current_user_role(request: fastapi.Request):
70
74
 
71
75
 
72
76
  @router.post('/create')
73
- async def user_create(user_create_body: payloads.UserCreateBody) -> None:
77
+ def user_create(user_create_body: payloads.UserCreateBody) -> None:
74
78
  username = user_create_body.username
75
79
  password = user_create_body.password
76
80
  role = user_create_body.role
@@ -100,8 +104,8 @@ async def user_create(user_create_body: payloads.UserCreateBody) -> None:
100
104
 
101
105
 
102
106
  @router.post('/update')
103
- async def user_update(request: fastapi.Request,
104
- user_update_body: payloads.UserUpdateBody) -> None:
107
+ def user_update(request: fastapi.Request,
108
+ user_update_body: payloads.UserUpdateBody) -> None:
105
109
  """Updates the user role."""
106
110
  user_id = user_update_body.user_id
107
111
  role = user_update_body.role
@@ -181,14 +185,13 @@ def _delete_user(user_id: str) -> None:
181
185
 
182
186
 
183
187
  @router.post('/delete')
184
- async def user_delete(user_delete_body: payloads.UserDeleteBody) -> None:
188
+ def user_delete(user_delete_body: payloads.UserDeleteBody) -> None:
185
189
  user_id = user_delete_body.user_id
186
190
  _delete_user(user_id)
187
191
 
188
192
 
189
193
  @router.post('/import')
190
- async def user_import(
191
- user_import_body: payloads.UserImportBody) -> Dict[str, Any]:
194
+ def user_import(user_import_body: payloads.UserImportBody) -> Dict[str, Any]:
192
195
  """Import users from CSV content."""
193
196
  csv_content = user_import_body.csv_content
194
197
 
@@ -305,7 +308,7 @@ async def user_import(
305
308
 
306
309
 
307
310
  @router.get('/export')
308
- async def user_export() -> Dict[str, Any]:
311
+ def user_export() -> Dict[str, Any]:
309
312
  """Export all users as CSV content."""
310
313
  try:
311
314
  # Get all users
@@ -369,7 +372,7 @@ def _user_lock(user_id: str) -> Generator[None, None, None]:
369
372
 
370
373
 
371
374
  @router.get('/service-account-tokens')
372
- async def get_service_account_tokens(
375
+ def get_service_account_tokens(
373
376
  request: fastapi.Request) -> List[Dict[str, Any]]:
374
377
  """Get service account tokens. All users can see all tokens."""
375
378
  auth_user = request.state.auth_user
@@ -420,7 +423,7 @@ def _generate_service_account_user_id() -> str:
420
423
 
421
424
 
422
425
  @router.post('/service-account-tokens')
423
- async def create_service_account_token(
426
+ def create_service_account_token(
424
427
  request: fastapi.Request,
425
428
  token_body: payloads.ServiceAccountTokenCreateBody) -> Dict[str, Any]:
426
429
  """Create a new service account token."""
@@ -508,7 +511,7 @@ async def create_service_account_token(
508
511
 
509
512
 
510
513
  @router.post('/service-account-tokens/delete')
511
- async def delete_service_account_token(
514
+ def delete_service_account_token(
512
515
  request: fastapi.Request,
513
516
  token_body: payloads.ServiceAccountTokenDeleteBody) -> Dict[str, str]:
514
517
  """Delete a service account token.
@@ -549,7 +552,7 @@ async def delete_service_account_token(
549
552
 
550
553
 
551
554
  @router.post('/service-account-tokens/get-role')
552
- async def get_service_account_role(
555
+ def get_service_account_role(
553
556
  request: fastapi.Request,
554
557
  role_body: payloads.ServiceAccountTokenRoleBody) -> Dict[str, Any]:
555
558
  """Get the role of a service account."""
@@ -585,7 +588,7 @@ async def get_service_account_role(
585
588
 
586
589
 
587
590
  @router.post('/service-account-tokens/update-role')
588
- async def update_service_account_role(
591
+ def update_service_account_role(
589
592
  request: fastapi.Request,
590
593
  role_body: payloads.ServiceAccountTokenUpdateRoleBody
591
594
  ) -> Dict[str, str]:
@@ -628,7 +631,7 @@ async def update_service_account_role(
628
631
 
629
632
 
630
633
  @router.post('/service-account-tokens/rotate')
631
- async def rotate_service_account_token(
634
+ def rotate_service_account_token(
632
635
  request: fastapi.Request,
633
636
  token_body: payloads.ServiceAccountTokenRotateBody) -> Dict[str, Any]:
634
637
  """Rotate a service account token.
sky/utils/db/db_utils.py CHANGED
@@ -2,10 +2,12 @@
2
2
  import asyncio
3
3
  import contextlib
4
4
  import enum
5
+ import os
6
+ import pathlib
5
7
  import sqlite3
6
8
  import threading
7
9
  import typing
8
- from typing import Any, Callable, Iterable, Optional
10
+ from typing import Any, Callable, Dict, Iterable, Optional
9
11
 
10
12
  import aiosqlite
11
13
  import aiosqlite.context
@@ -13,6 +15,7 @@ import sqlalchemy
13
15
  from sqlalchemy import exc as sqlalchemy_exc
14
16
 
15
17
  from sky import sky_logging
18
+ from sky.skylet import constants
16
19
 
17
20
  logger = sky_logging.init_logger(__name__)
18
21
  if typing.TYPE_CHECKING:
@@ -346,3 +349,57 @@ class SQLiteConn(threading.local):
346
349
  ) -> Iterable[sqlite3.Row]:
347
350
  conn = await self._get_async_conn()
348
351
  return await conn.execute_fetchall(sql, parameters)
352
+
353
+ async def close(self):
354
+ if self._async_conn is not None:
355
+ await self._async_conn.close()
356
+ self.conn.close()
357
+
358
+
359
+ _max_connections = 0
360
+ _postgres_engine_cache: Dict[str, sqlalchemy.engine.Engine] = {}
361
+ _sqlite_engine_cache: Dict[str, sqlalchemy.engine.Engine] = {}
362
+
363
+ _db_creation_lock = threading.Lock()
364
+
365
+
366
+ def set_max_connections(max_connections: int):
367
+ global _max_connections
368
+ _max_connections = max_connections
369
+
370
+
371
+ def get_max_connections():
372
+ return _max_connections
373
+
374
+
375
+ def get_engine(db_name: str):
376
+ conn_string = None
377
+ if os.environ.get(constants.ENV_VAR_IS_SKYPILOT_SERVER) is not None:
378
+ conn_string = os.environ.get(constants.ENV_VAR_DB_CONNECTION_URI)
379
+ if conn_string:
380
+ with _db_creation_lock:
381
+ if conn_string not in _postgres_engine_cache:
382
+ if _max_connections == 0:
383
+ _postgres_engine_cache[conn_string] = (
384
+ sqlalchemy.create_engine(
385
+ conn_string, poolclass=sqlalchemy.pool.NullPool))
386
+ elif _max_connections == 1:
387
+ _postgres_engine_cache[conn_string] = (
388
+ sqlalchemy.create_engine(
389
+ conn_string, poolclass=sqlalchemy.pool.StaticPool))
390
+ else:
391
+ _postgres_engine_cache[conn_string] = (
392
+ sqlalchemy.create_engine(
393
+ conn_string,
394
+ poolclass=sqlalchemy.pool.QueuePool,
395
+ size=_max_connections,
396
+ max_overflow=0))
397
+ engine = _postgres_engine_cache[conn_string]
398
+ else:
399
+ db_path = os.path.expanduser(f'~/.sky/{db_name}.db')
400
+ pathlib.Path(db_path).parents[0].mkdir(parents=True, exist_ok=True)
401
+ if db_path not in _sqlite_engine_cache:
402
+ _sqlite_engine_cache[db_path] = sqlalchemy.create_engine(
403
+ 'sqlite:///' + db_path)
404
+ engine = _sqlite_engine_cache[db_path]
405
+ return engine
@@ -3,9 +3,6 @@
3
3
  import contextlib
4
4
  import logging
5
5
  import os
6
- import pathlib
7
- import threading
8
- from typing import Dict, Optional
9
6
 
10
7
  from alembic import command as alembic_command
11
8
  from alembic.config import Config
@@ -14,7 +11,6 @@ import filelock
14
11
  import sqlalchemy
15
12
 
16
13
  from sky import sky_logging
17
- from sky.skylet import constants
18
14
 
19
15
  logger = sky_logging.init_logger(__name__)
20
16
 
@@ -32,34 +28,6 @@ SERVE_DB_NAME = 'serve_db'
32
28
  SERVE_VERSION = '001'
33
29
  SERVE_LOCK_PATH = '~/.sky/locks/.serve_db.lock'
34
30
 
35
- _postgres_engine_cache: Dict[str, sqlalchemy.engine.Engine] = {}
36
- _sqlite_engine_cache: Dict[str, sqlalchemy.engine.Engine] = {}
37
-
38
- _db_creation_lock = threading.Lock()
39
-
40
-
41
- def get_engine(db_name: str,
42
- pg_pool_class: Optional[sqlalchemy.pool.Pool] = None):
43
- conn_string = None
44
- if os.environ.get(constants.ENV_VAR_IS_SKYPILOT_SERVER) is not None:
45
- conn_string = os.environ.get(constants.ENV_VAR_DB_CONNECTION_URI)
46
- if conn_string:
47
- if pg_pool_class is None:
48
- pg_pool_class = sqlalchemy.NullPool
49
- with _db_creation_lock:
50
- if conn_string not in _postgres_engine_cache:
51
- _postgres_engine_cache[conn_string] = sqlalchemy.create_engine(
52
- conn_string, poolclass=pg_pool_class)
53
- engine = _postgres_engine_cache[conn_string]
54
- else:
55
- db_path = os.path.expanduser(f'~/.sky/{db_name}.db')
56
- pathlib.Path(db_path).parents[0].mkdir(parents=True, exist_ok=True)
57
- if db_path not in _sqlite_engine_cache:
58
- _sqlite_engine_cache[db_path] = sqlalchemy.create_engine(
59
- 'sqlite:///' + db_path)
60
- engine = _sqlite_engine_cache[db_path]
61
- return engine
62
-
63
31
 
64
32
  @contextlib.contextmanager
65
33
  def db_lock(db_name: str):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: skypilot-nightly
3
- Version: 1.0.0.dev20250903
3
+ Version: 1.0.0.dev20250904
4
4
  Summary: SkyPilot: Run AI on Any Infra — Unified, Faster, Cheaper.
5
5
  Author: SkyPilot Team
6
6
  License: Apache 2.0
@@ -145,48 +145,48 @@ Requires-Dist: grpcio>=1.63.0; extra == "server"
145
145
  Requires-Dist: protobuf<7.0.0,>=5.26.1; extra == "server"
146
146
  Requires-Dist: aiosqlite; extra == "server"
147
147
  Provides-Extra: all
148
- Requires-Dist: ibm-cos-sdk; extra == "all"
149
- Requires-Dist: ray[default]!=2.6.0,>=2.2.0; extra == "all"
150
- Requires-Dist: vastai-sdk>=0.1.12; extra == "all"
151
- Requires-Dist: aiohttp; extra == "all"
152
- Requires-Dist: google-cloud-storage; extra == "all"
153
- Requires-Dist: websockets; extra == "all"
154
- Requires-Dist: azure-mgmt-compute>=33.0.0; extra == "all"
148
+ Requires-Dist: runpod>=1.6.1; extra == "all"
149
+ Requires-Dist: azure-common; extra == "all"
155
150
  Requires-Dist: kubernetes!=32.0.0,>=20.0.0; extra == "all"
156
- Requires-Dist: msgraph-sdk; extra == "all"
157
- Requires-Dist: azure-storage-blob>=12.23.1; extra == "all"
158
- Requires-Dist: anyio; extra == "all"
159
- Requires-Dist: boto3>=1.26.1; extra == "all"
160
- Requires-Dist: google-api-python-client>=2.69.0; extra == "all"
161
- Requires-Dist: pyopenssl<24.3.0,>=23.2.0; extra == "all"
162
- Requires-Dist: cudo-compute>=0.1.10; extra == "all"
163
- Requires-Dist: azure-mgmt-network>=27.0.0; extra == "all"
151
+ Requires-Dist: aiohttp; extra == "all"
164
152
  Requires-Dist: pyvmomi==8.0.1.0.2; extra == "all"
165
- Requires-Dist: passlib; extra == "all"
166
- Requires-Dist: azure-identity>=1.19.0; extra == "all"
167
- Requires-Dist: botocore>=1.29.10; extra == "all"
168
- Requires-Dist: awscli>=1.27.10; extra == "all"
169
- Requires-Dist: python-dateutil; extra == "all"
170
- Requires-Dist: pyjwt; extra == "all"
171
153
  Requires-Dist: oci; extra == "all"
172
- Requires-Dist: ibm-cloud-sdk-core; extra == "all"
154
+ Requires-Dist: ray[default]!=2.6.0,>=2.2.0; extra == "all"
155
+ Requires-Dist: ibm-platform-services>=0.48.0; extra == "all"
156
+ Requires-Dist: pyjwt; extra == "all"
173
157
  Requires-Dist: azure-cli>=2.65.0; extra == "all"
174
- Requires-Dist: aiosqlite; extra == "all"
175
- Requires-Dist: pydo>=0.3.0; extra == "all"
176
- Requires-Dist: docker; extra == "all"
177
- Requires-Dist: azure-common; extra == "all"
178
- Requires-Dist: colorama<0.4.5; extra == "all"
179
- Requires-Dist: msrestazure; extra == "all"
158
+ Requires-Dist: ibm-cos-sdk; extra == "all"
159
+ Requires-Dist: python-dateutil; extra == "all"
180
160
  Requires-Dist: ibm-vpc; extra == "all"
181
- Requires-Dist: ibm-platform-services>=0.48.0; extra == "all"
161
+ Requires-Dist: pyopenssl<24.3.0,>=23.2.0; extra == "all"
162
+ Requires-Dist: grpcio>=1.63.0; extra == "all"
163
+ Requires-Dist: boto3>=1.26.1; extra == "all"
164
+ Requires-Dist: colorama<0.4.5; extra == "all"
165
+ Requires-Dist: azure-storage-blob>=12.23.1; extra == "all"
182
166
  Requires-Dist: azure-core>=1.24.0; extra == "all"
167
+ Requires-Dist: azure-mgmt-compute>=33.0.0; extra == "all"
168
+ Requires-Dist: anyio; extra == "all"
183
169
  Requires-Dist: sqlalchemy_adapter; extra == "all"
170
+ Requires-Dist: pydo>=0.3.0; extra == "all"
171
+ Requires-Dist: azure-mgmt-network>=27.0.0; extra == "all"
172
+ Requires-Dist: casbin; extra == "all"
173
+ Requires-Dist: websockets; extra == "all"
174
+ Requires-Dist: aiosqlite; extra == "all"
175
+ Requires-Dist: botocore>=1.29.10; extra == "all"
184
176
  Requires-Dist: azure-core>=1.31.0; extra == "all"
177
+ Requires-Dist: ibm-cloud-sdk-core; extra == "all"
178
+ Requires-Dist: vastai-sdk>=0.1.12; extra == "all"
179
+ Requires-Dist: docker; extra == "all"
185
180
  Requires-Dist: nebius>=0.2.47; extra == "all"
186
- Requires-Dist: casbin; extra == "all"
181
+ Requires-Dist: google-cloud-storage; extra == "all"
182
+ Requires-Dist: msgraph-sdk; extra == "all"
183
+ Requires-Dist: awscli>=1.27.10; extra == "all"
187
184
  Requires-Dist: protobuf<7.0.0,>=5.26.1; extra == "all"
188
- Requires-Dist: grpcio>=1.63.0; extra == "all"
189
- Requires-Dist: runpod>=1.6.1; extra == "all"
185
+ Requires-Dist: google-api-python-client>=2.69.0; extra == "all"
186
+ Requires-Dist: msrestazure; extra == "all"
187
+ Requires-Dist: cudo-compute>=0.1.10; extra == "all"
188
+ Requires-Dist: azure-identity>=1.19.0; extra == "all"
189
+ Requires-Dist: passlib; extra == "all"
190
190
  Dynamic: author
191
191
  Dynamic: classifier
192
192
  Dynamic: description