coiled 1.128.3.dev1__tar.gz → 1.130.2.dev11__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.
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/PKG-INFO +1 -1
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/capture_environment.py +57 -82
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/cli/hello/hello.py +1 -1
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/cli/setup/azure.py +85 -8
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/context.py +2 -2
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/core.py +21 -2
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/filestore.py +1 -1
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/pypi_conda_map.py +22 -96
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/software.py +18 -3
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/software_utils.py +33 -11
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/types.py +2 -0
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/v2/cluster.py +0 -1
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/v2/core.py +11 -1
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/v2/widgets/rich.py +2 -1
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/.gitignore +0 -0
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/LICENSE +0 -0
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/README.md +0 -0
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/__init__.py +0 -0
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/__main__.py +0 -0
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/analytics.py +0 -0
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/auth.py +0 -0
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/batch.py +0 -0
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/cli/__init__.py +0 -0
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/cli/batch/__init__.py +0 -0
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/cli/batch/list.py +0 -0
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/cli/batch/logs.py +0 -0
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/cli/batch/run.py +0 -0
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/cli/batch/status.py +0 -0
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/cli/batch/util.py +0 -0
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/cli/batch/wait.py +0 -0
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/cli/cluster/__init__.py +0 -0
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/cli/cluster/azure_logs.py +0 -0
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/cli/cluster/better_logs.py +0 -0
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/cli/cluster/crud.py +0 -0
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/cli/cluster/get_address.py +0 -0
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/cli/cluster/list.py +0 -0
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/cli/cluster/logs.py +0 -0
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/cli/cluster/metrics.py +0 -0
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/cli/cluster/ssh.py +0 -0
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/cli/cluster/utils.py +0 -0
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/cli/config.py +0 -0
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/cli/core.py +0 -0
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/cli/curl.py +0 -0
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/cli/diagnostics.py +0 -0
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/cli/env.py +0 -0
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/cli/file.py +0 -0
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/cli/hello/__init__.py +0 -0
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/cli/hello/examples/__init__.py +0 -0
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/cli/hello/examples/exit.py +0 -0
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/cli/hello/examples/hello_world.py +0 -0
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/cli/hello/examples/nyc_parquet.py +0 -0
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/cli/hello/examples/pytorch.py +0 -0
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/cli/hello/examples/xarray_nwm.py +0 -0
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/cli/hello/scripts/fill_ipython.py +0 -0
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/cli/hello/scripts/nyc_parquet.py +0 -0
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/cli/hello/scripts/pytorch.py +0 -0
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/cli/hello/scripts/xarray_nwm.py +0 -0
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/cli/hello/utils.py +0 -0
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/cli/login.py +0 -0
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/cli/mpi.py +0 -0
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/cli/notebook/__init__.py +0 -0
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/cli/notebook/notebook.py +0 -0
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/cli/package_sync.py +0 -0
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/cli/prefect.py +0 -0
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/cli/prefect_serve.py +0 -0
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/cli/run.py +0 -0
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/cli/setup/__init__.py +0 -0
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/cli/setup/amp.py +0 -0
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/cli/setup/aws.py +0 -0
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/cli/setup/entry.py +0 -0
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/cli/setup/gcp.py +0 -0
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/cli/setup/prometheus.py +0 -0
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/cli/setup/util.py +0 -0
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/cli/sync.py +0 -0
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/cli/utils.py +0 -0
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/cluster.py +0 -0
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/coiled.yaml +0 -0
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/compatibility.py +0 -0
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/config.py +0 -0
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/credentials/__init__.py +0 -0
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/credentials/aws.py +0 -0
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/credentials/google.py +0 -0
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/errors.py +0 -0
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/exceptions.py +0 -0
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/extensions/__init__.py +0 -0
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/extensions/prefect/__init__.py +0 -0
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/extensions/prefect/runners.py +0 -0
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/extensions/prefect/workers.py +0 -0
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/function.py +0 -0
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/plugins.py +0 -0
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/prefect.py +0 -0
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/scan.py +0 -0
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/spans.py +0 -0
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/spark.py +0 -0
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/utils.py +0 -0
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/v2/__init__.py +0 -0
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/v2/cluster_comms.py +0 -0
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/v2/cwi_log_link.py +0 -0
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/v2/states.py +0 -0
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/v2/widgets/__init__.py +0 -0
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/v2/widgets/interface.py +0 -0
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/v2/widgets/util.py +0 -0
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/coiled/websockets.py +0 -0
- {coiled-1.128.3.dev1 → coiled-1.130.2.dev11}/pyproject.toml +0 -0
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import asyncio
|
|
2
1
|
import contextlib
|
|
3
|
-
import logging
|
|
4
2
|
import platform
|
|
5
3
|
import sys
|
|
6
4
|
import typing
|
|
@@ -16,9 +14,12 @@ from typing_extensions import Literal
|
|
|
16
14
|
from coiled.context import track_context
|
|
17
15
|
from coiled.scan import scan_prefix
|
|
18
16
|
from coiled.software_utils import (
|
|
17
|
+
ANY_AVAILABLE,
|
|
18
|
+
PYTHON_VERSION,
|
|
19
19
|
check_pip_happy,
|
|
20
20
|
create_wheels_for_local_python,
|
|
21
21
|
create_wheels_for_packages,
|
|
22
|
+
get_lockfile,
|
|
22
23
|
partition_ignored_packages,
|
|
23
24
|
partition_local_packages,
|
|
24
25
|
partition_local_python_code_packages,
|
|
@@ -36,10 +37,6 @@ from coiled.v2.core import CloudV2
|
|
|
36
37
|
from coiled.v2.widgets.rich import CONSOLE_WIDTH, print_rich_package_table
|
|
37
38
|
from coiled.v2.widgets.util import simple_progress, use_rich_widget
|
|
38
39
|
|
|
39
|
-
PYTHON_VERSION = platform.python_version_tuple()
|
|
40
|
-
ANY_AVAILABLE = "ANY-AVAILABLE"
|
|
41
|
-
|
|
42
|
-
|
|
43
40
|
logger = getLogger("coiled.package_sync")
|
|
44
41
|
|
|
45
42
|
|
|
@@ -69,49 +66,53 @@ async def approximate_packages(
|
|
|
69
66
|
architecture: ArchitectureTypesEnum = ArchitectureTypesEnum.X86_64,
|
|
70
67
|
pip_check_errors: Optional[Dict[str, List[str]]] = None,
|
|
71
68
|
gpu_enabled: bool = False,
|
|
69
|
+
use_uv_installer: bool = True,
|
|
70
|
+
lockfile_path: Optional[Path] = None,
|
|
72
71
|
) -> typing.List[ResolvedPackageInfo]:
|
|
73
72
|
user_conda_installed_python = next((p for p in packages if p["name"] == "python"), None)
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
if not user_conda_installed_pip:
|
|
79
|
-
# This means pip was installed by pip, or the system
|
|
80
|
-
# package manager
|
|
81
|
-
# Insert a conda version of pip to be installed first, it will
|
|
82
|
-
# then be used to install the users version of pip
|
|
83
|
-
pip = next(
|
|
84
|
-
(p for p in packages if p["name"] == "pip" and p["source"] == "pip"),
|
|
73
|
+
# Only add pip if we need it
|
|
74
|
+
if not use_uv_installer:
|
|
75
|
+
user_conda_installed_pip = next(
|
|
76
|
+
(i for i, p in enumerate(packages) if p["name"] == "pip" and p["source"] == "conda"),
|
|
85
77
|
None,
|
|
86
78
|
)
|
|
87
|
-
if not
|
|
88
|
-
#
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
"
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
79
|
+
if not user_conda_installed_pip:
|
|
80
|
+
# This means pip was installed by pip, or the system
|
|
81
|
+
# package manager
|
|
82
|
+
# Insert a conda version of pip to be installed first, it will
|
|
83
|
+
# then be used to install the users version of pip
|
|
84
|
+
pip = next(
|
|
85
|
+
(p for p in packages if p["name"] == "pip" and p["source"] == "pip"),
|
|
86
|
+
None,
|
|
87
|
+
)
|
|
88
|
+
if not pip:
|
|
89
|
+
# insert a modern version and hope it does not introduce conflicts
|
|
90
|
+
packages.append({
|
|
91
|
+
"name": "pip",
|
|
92
|
+
"path": None,
|
|
93
|
+
"source": "conda",
|
|
94
|
+
"channel_url": "https://conda.anaconda.org/conda-forge/",
|
|
95
|
+
"channel": "conda-forge",
|
|
96
|
+
"subdir": "noarch",
|
|
97
|
+
"conda_name": "pip",
|
|
98
|
+
"version": "22.3.1",
|
|
99
|
+
"wheel_target": None,
|
|
100
|
+
"requested": False,
|
|
101
|
+
})
|
|
102
|
+
else:
|
|
103
|
+
# insert the users pip version and hope it exists on conda-forge
|
|
104
|
+
packages.append({
|
|
105
|
+
"name": "pip",
|
|
106
|
+
"path": None,
|
|
107
|
+
"source": "conda",
|
|
108
|
+
"channel_url": "https://conda.anaconda.org/conda-forge/",
|
|
109
|
+
"channel": "conda-forge",
|
|
110
|
+
"subdir": "noarch",
|
|
111
|
+
"conda_name": "pip",
|
|
112
|
+
"version": pip["version"],
|
|
113
|
+
"wheel_target": None,
|
|
114
|
+
"requested": True,
|
|
115
|
+
})
|
|
115
116
|
coiled_selected_python = None
|
|
116
117
|
if not user_conda_installed_python:
|
|
117
118
|
# insert a special python package
|
|
@@ -162,6 +163,8 @@ async def approximate_packages(
|
|
|
162
163
|
architecture=architecture,
|
|
163
164
|
pip_check_errors=pip_check_errors,
|
|
164
165
|
gpu_enabled=gpu_enabled,
|
|
166
|
+
lockfile_name=lockfile_path.name if lockfile_path else None,
|
|
167
|
+
lockfile_content=lockfile_path.read_text() if lockfile_path else None,
|
|
165
168
|
)
|
|
166
169
|
finalized_packages: typing.List[ResolvedPackageInfo] = []
|
|
167
170
|
finalized_packages.extend(await create_wheels_for_local_python(local_python_code, progress=progress))
|
|
@@ -208,6 +211,7 @@ async def create_environment_approximation(
|
|
|
208
211
|
progress: Optional[Progress] = None,
|
|
209
212
|
architecture: ArchitectureTypesEnum = ArchitectureTypesEnum.X86_64,
|
|
210
213
|
gpu_enabled: bool = False,
|
|
214
|
+
use_uv_installer: bool = True,
|
|
211
215
|
) -> typing.List[ResolvedPackageInfo]:
|
|
212
216
|
packages = await scan_prefix(progress=progress)
|
|
213
217
|
pip_check_errors = await check_pip_happy(progress)
|
|
@@ -237,6 +241,8 @@ async def create_environment_approximation(
|
|
|
237
241
|
architecture=architecture,
|
|
238
242
|
pip_check_errors=pip_check_errors,
|
|
239
243
|
gpu_enabled=gpu_enabled,
|
|
244
|
+
use_uv_installer=use_uv_installer,
|
|
245
|
+
lockfile_path=get_lockfile(),
|
|
240
246
|
)
|
|
241
247
|
return result
|
|
242
248
|
|
|
@@ -259,7 +265,7 @@ async def scan_and_create(
|
|
|
259
265
|
):
|
|
260
266
|
use_widget = force_rich_widget or (show_widget and use_rich_widget())
|
|
261
267
|
|
|
262
|
-
local_env_name = Path(sys.prefix).name
|
|
268
|
+
local_env_name = str(get_lockfile() or Path(sys.prefix).name)
|
|
263
269
|
if use_widget:
|
|
264
270
|
progress = Progress(TextColumn("[progress.description]{task.description}"), BarColumn(), TimeElapsedColumn())
|
|
265
271
|
live = Live(Panel(progress, title=f"[green]Package Sync for {local_env_name}", width=CONSOLE_WIDTH))
|
|
@@ -268,6 +274,9 @@ async def scan_and_create(
|
|
|
268
274
|
progress = None
|
|
269
275
|
|
|
270
276
|
with live:
|
|
277
|
+
# We do this even with lockfiles because some early checks happen
|
|
278
|
+
# on this endpoint to prevent people getting delayed quota errors
|
|
279
|
+
# TODO: Add a lighter weight endpoint that does just these checks
|
|
271
280
|
with simple_progress("Fetching latest package priorities", progress):
|
|
272
281
|
logger.info(f"Resolving your local {local_env_name} Python environment...")
|
|
273
282
|
async with (
|
|
@@ -306,6 +315,7 @@ async def scan_and_create(
|
|
|
306
315
|
architecture=architecture,
|
|
307
316
|
gpu_enabled=gpu_enabled,
|
|
308
317
|
conda_extras=package_sync_conda_extras,
|
|
318
|
+
use_uv_installer=use_uv_installer,
|
|
309
319
|
)
|
|
310
320
|
|
|
311
321
|
if not package_sync_only:
|
|
@@ -367,6 +377,7 @@ async def scan_and_create(
|
|
|
367
377
|
# default region in declarative service create_software_environment
|
|
368
378
|
region_name=region_name,
|
|
369
379
|
use_uv_installer=use_uv_installer,
|
|
380
|
+
lockfile_path=get_lockfile(),
|
|
370
381
|
)
|
|
371
382
|
if use_widget:
|
|
372
383
|
print_rich_package_table(packages_with_notes, packages_with_errors)
|
|
@@ -427,39 +438,3 @@ If you use pip, venv, uv, pixi, etc. create a new environment and then:
|
|
|
427
438
|
|
|
428
439
|
See https://docs.coiled.io/user_guide/software/package_sync_best_practices.html
|
|
429
440
|
for more best practices. If that doesn't solve your issue, please contact support@coiled.io.""")
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
if __name__ == "__main__":
|
|
433
|
-
from logging import basicConfig
|
|
434
|
-
|
|
435
|
-
basicConfig(level=logging.INFO)
|
|
436
|
-
|
|
437
|
-
from rich.console import Console
|
|
438
|
-
from rich.table import Table
|
|
439
|
-
|
|
440
|
-
async def run():
|
|
441
|
-
async with CloudV2(asynchronous=True) as cloud:
|
|
442
|
-
return await create_environment_approximation(
|
|
443
|
-
cloud=cloud,
|
|
444
|
-
priorities={
|
|
445
|
-
("dask", "conda"): PackageLevelEnum.CRITICAL,
|
|
446
|
-
("twisted", "conda"): PackageLevelEnum.IGNORE,
|
|
447
|
-
("graphviz", "conda"): PackageLevelEnum.LOOSE,
|
|
448
|
-
("icu", "conda"): PackageLevelEnum.LOOSE,
|
|
449
|
-
},
|
|
450
|
-
)
|
|
451
|
-
|
|
452
|
-
result = asyncio.run(run())
|
|
453
|
-
|
|
454
|
-
table = Table(title="Packages")
|
|
455
|
-
keys = ("name", "source", "include", "client_version", "specifier", "error", "note")
|
|
456
|
-
|
|
457
|
-
for key in keys:
|
|
458
|
-
table.add_column(key)
|
|
459
|
-
|
|
460
|
-
for pkg in result:
|
|
461
|
-
row_values = [str(pkg.get(key, "")) for key in keys]
|
|
462
|
-
table.add_row(*row_values)
|
|
463
|
-
console = Console()
|
|
464
|
-
console.print(table)
|
|
465
|
-
console.print(table)
|
|
@@ -295,7 +295,7 @@ Choose any computation you'd like to run:
|
|
|
295
295
|
Yee-haw you've done all my examples 🎉
|
|
296
296
|
Now you can:
|
|
297
297
|
- Try Coiled in your own use case
|
|
298
|
-
- [
|
|
298
|
+
- [Ask us questions](mailto:support@coiled.io)
|
|
299
299
|
- Explore the [docs](https://docs.coiled.io?utm_source=coiled-hello&utm_medium=finished) to see all the other things Coiled can do
|
|
300
300
|
"""), # noqa
|
|
301
301
|
border_style="green",
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import datetime
|
|
2
4
|
import json
|
|
3
5
|
import os
|
|
@@ -6,7 +8,6 @@ import shutil
|
|
|
6
8
|
import subprocess
|
|
7
9
|
import sys
|
|
8
10
|
import time
|
|
9
|
-
from typing import Optional
|
|
10
11
|
|
|
11
12
|
import click
|
|
12
13
|
import httpx
|
|
@@ -158,6 +159,12 @@ coiled curl -X POST "${SETUP_ENDPOINT}" --json --data "{\\"credentials\\": {\\"t
|
|
|
158
159
|
is_flag=True,
|
|
159
160
|
help="If there's existing enterprise application, add new secret rather than replacing any existing ones.",
|
|
160
161
|
)
|
|
162
|
+
@click.option(
|
|
163
|
+
"--update-role-definitions",
|
|
164
|
+
default=False,
|
|
165
|
+
is_flag=True,
|
|
166
|
+
help="If Role Definitions already exist, then update them; default is to leave them unchanged if they exist.",
|
|
167
|
+
)
|
|
161
168
|
@click.option(
|
|
162
169
|
"--save-script",
|
|
163
170
|
is_flag=True,
|
|
@@ -175,8 +182,22 @@ coiled curl -X POST "${SETUP_ENDPOINT}" --json --data "{\\"credentials\\": {\\"t
|
|
|
175
182
|
"service principal for Coiled to use."
|
|
176
183
|
),
|
|
177
184
|
)
|
|
185
|
+
@click.option(
|
|
186
|
+
"--refresh-for-app-id", default=None, help="Refresh the secret key used by Coiled for specified Application ID."
|
|
187
|
+
)
|
|
178
188
|
@click.command(context_settings=CONTEXT_SETTINGS)
|
|
179
|
-
def azure_setup(
|
|
189
|
+
def azure_setup(
|
|
190
|
+
subscription,
|
|
191
|
+
resource_group,
|
|
192
|
+
region,
|
|
193
|
+
account,
|
|
194
|
+
iam_user,
|
|
195
|
+
keep_existing_access,
|
|
196
|
+
update_role_definitions,
|
|
197
|
+
save_script,
|
|
198
|
+
ship_token,
|
|
199
|
+
refresh_for_app_id,
|
|
200
|
+
):
|
|
180
201
|
print(
|
|
181
202
|
"Coiled on Azure is currently in [bold]public beta[/bold], "
|
|
182
203
|
"please contact [link]support@coiled.io[/link] if you have any questions or problems."
|
|
@@ -264,6 +285,17 @@ def azure_setup(subscription, resource_group, region, account, iam_user, keep_ex
|
|
|
264
285
|
f"with [green]{region}[/green] as the default region\n"
|
|
265
286
|
)
|
|
266
287
|
|
|
288
|
+
if refresh_for_app_id:
|
|
289
|
+
refresh_app_creds(
|
|
290
|
+
app_id=refresh_for_app_id,
|
|
291
|
+
coiled_account=coiled_account,
|
|
292
|
+
sub_id=sub_id,
|
|
293
|
+
rg_name=rg_name,
|
|
294
|
+
region=region,
|
|
295
|
+
keep_existing_keys=True,
|
|
296
|
+
)
|
|
297
|
+
return
|
|
298
|
+
|
|
267
299
|
if ship_token:
|
|
268
300
|
enable_providers(creds, sub_id)
|
|
269
301
|
ship_token_creds(
|
|
@@ -289,7 +321,15 @@ def azure_setup(subscription, resource_group, region, account, iam_user, keep_ex
|
|
|
289
321
|
app_name = iam_user or f"coiled-{coiled_account}-app"
|
|
290
322
|
try:
|
|
291
323
|
if not setup_with_service_principal(
|
|
292
|
-
creds,
|
|
324
|
+
creds,
|
|
325
|
+
app_name,
|
|
326
|
+
sub_id,
|
|
327
|
+
rg_name,
|
|
328
|
+
rg_id,
|
|
329
|
+
coiled_account,
|
|
330
|
+
region,
|
|
331
|
+
keep_existing_keys=keep_existing_access,
|
|
332
|
+
update_role_definitions=update_role_definitions,
|
|
293
333
|
):
|
|
294
334
|
coiled.add_interaction(action="CoiledSetup", success=False)
|
|
295
335
|
except Exception as e:
|
|
@@ -301,7 +341,15 @@ def azure_setup(subscription, resource_group, region, account, iam_user, keep_ex
|
|
|
301
341
|
|
|
302
342
|
|
|
303
343
|
def setup_with_service_principal(
|
|
304
|
-
creds,
|
|
344
|
+
creds,
|
|
345
|
+
app_name,
|
|
346
|
+
sub_id,
|
|
347
|
+
rg_name,
|
|
348
|
+
rg_id,
|
|
349
|
+
coiled_account,
|
|
350
|
+
region,
|
|
351
|
+
keep_existing_keys: bool = False,
|
|
352
|
+
update_role_definitions: bool = False,
|
|
305
353
|
):
|
|
306
354
|
prompt = f"Create [green]{app_name}[/green] service principal and grant Coiled access to your Azure subscription?"
|
|
307
355
|
if not Confirm.ask(prompt, default=True):
|
|
@@ -341,12 +389,16 @@ def setup_with_service_principal(
|
|
|
341
389
|
|
|
342
390
|
az_cli_wrapper(
|
|
343
391
|
f"az role definition create --role-definition @{role_def_path}",
|
|
344
|
-
command_if_exists=f"az role definition update --role-definition @{role_def_path}"
|
|
392
|
+
command_if_exists=f"az role definition update --role-definition @{role_def_path}"
|
|
393
|
+
if update_role_definitions
|
|
394
|
+
else None,
|
|
345
395
|
)
|
|
346
396
|
print(f" [bright_black]Creating/updating role definition {LOG_ROLE_NAME}...")
|
|
347
397
|
az_cli_wrapper(
|
|
348
398
|
f"az role definition create --role-definition @{log_role_def_path}",
|
|
349
|
-
command_if_exists=f"az role definition update --role-definition @{log_role_def_path}"
|
|
399
|
+
command_if_exists=f"az role definition update --role-definition @{log_role_def_path}"
|
|
400
|
+
if update_role_definitions
|
|
401
|
+
else None,
|
|
350
402
|
)
|
|
351
403
|
|
|
352
404
|
print(f" [bright_black]Assigning '{RG_ROLE_NAME}' role to service principal on '{rg_name}' resource group...")
|
|
@@ -378,6 +430,31 @@ def setup_with_service_principal(
|
|
|
378
430
|
return True
|
|
379
431
|
|
|
380
432
|
|
|
433
|
+
def refresh_app_creds(app_id, keep_existing_keys, coiled_account, sub_id, rg_name, region):
|
|
434
|
+
print(f" [bright_black]Resetting/retrieving credentials for {app_id}...")
|
|
435
|
+
cred_reset_opts = "--append" if keep_existing_keys else ""
|
|
436
|
+
app_creds_json = az_cli_wrapper(f"az ad app credential reset --id {app_id} {cred_reset_opts}")
|
|
437
|
+
app_creds = json.loads(app_creds_json)
|
|
438
|
+
|
|
439
|
+
creds_to_submit = {
|
|
440
|
+
"tenant_id": app_creds["tenant"],
|
|
441
|
+
"client_id": app_creds["appId"],
|
|
442
|
+
"client_secret": app_creds["password"],
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
print("Sending Azure credentials to Coiled... ", end="")
|
|
446
|
+
submit_azure_credentials(
|
|
447
|
+
coiled_account=coiled_account,
|
|
448
|
+
sub_id=sub_id,
|
|
449
|
+
rg_name=rg_name,
|
|
450
|
+
region=region,
|
|
451
|
+
creds_to_submit=creds_to_submit,
|
|
452
|
+
)
|
|
453
|
+
print(f"Azure credentials have been updated for {coiled_account} using Azure app {app_id} and {rg_name}!")
|
|
454
|
+
|
|
455
|
+
coiled.add_interaction(action="RefreshCloudCredentials", success=True)
|
|
456
|
+
|
|
457
|
+
|
|
381
458
|
def submit_azure_credentials(coiled_account, sub_id, rg_name, region, creds_to_submit, check_after: bool = False):
|
|
382
459
|
with coiled.Cloud(account=coiled_account) as cloud:
|
|
383
460
|
setup_endpoint = f"/api/v2/cloud-credentials/{coiled_account}/azure"
|
|
@@ -399,13 +476,13 @@ def strip_output(output: str) -> str:
|
|
|
399
476
|
return output.strip(' \n"')
|
|
400
477
|
|
|
401
478
|
|
|
402
|
-
def get_cli_path() ->
|
|
479
|
+
def get_cli_path() -> str | None:
|
|
403
480
|
return shutil.which("az")
|
|
404
481
|
|
|
405
482
|
|
|
406
483
|
def az_cli_wrapper(
|
|
407
484
|
command: str,
|
|
408
|
-
command_if_exists: str = "",
|
|
485
|
+
command_if_exists: str | None = "",
|
|
409
486
|
show_stdout: bool = False,
|
|
410
487
|
interactive: bool = False,
|
|
411
488
|
):
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import asyncio
|
|
4
3
|
import functools
|
|
4
|
+
import inspect
|
|
5
5
|
import random
|
|
6
6
|
import string
|
|
7
7
|
from contextlib import contextmanager, nullcontext
|
|
@@ -103,7 +103,7 @@ def get_trace_context(func: Union[SyncFuncType, AsyncFuncType]):
|
|
|
103
103
|
|
|
104
104
|
|
|
105
105
|
def track_context(func: F) -> F:
|
|
106
|
-
if
|
|
106
|
+
if inspect.iscoroutinefunction(func):
|
|
107
107
|
|
|
108
108
|
@functools.wraps(func)
|
|
109
109
|
async def async_wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
@@ -924,6 +924,7 @@ class Cloud(Generic[IsAsynchronous]):
|
|
|
924
924
|
include_local_code: bool = False,
|
|
925
925
|
ignore_local_packages: List[str] | None = None,
|
|
926
926
|
use_uv_installer: bool = True,
|
|
927
|
+
lockfile_path: Union[str, pathlib.Path, None] = None,
|
|
927
928
|
) -> SoftwareEnvironmentAlias | None:
|
|
928
929
|
if name is None and conda is not None and isinstance(conda, dict) and "name" in conda:
|
|
929
930
|
name = conda["name"]
|
|
@@ -943,8 +944,20 @@ class Cloud(Generic[IsAsynchronous]):
|
|
|
943
944
|
raise TypeError("The build backend does not support specifying both packages and a container")
|
|
944
945
|
if container and include_local_code:
|
|
945
946
|
raise TypeError("The build backend does not support including local code when using a container")
|
|
946
|
-
if
|
|
947
|
-
|
|
947
|
+
if lockfile_path:
|
|
948
|
+
if pip or conda:
|
|
949
|
+
raise TypeError("The build backend does not support specifying both a lockfile and packages")
|
|
950
|
+
lockfile_path = pathlib.Path(lockfile_path)
|
|
951
|
+
if not lockfile_path.exists():
|
|
952
|
+
raise FileNotFoundError(f"Lockfile not found: {lockfile_path}")
|
|
953
|
+
if not lockfile_path.name.endswith(("uv.lock", "pylock.toml", "conda-lock.yml")):
|
|
954
|
+
logger.warning(
|
|
955
|
+
"The specified lockfile does not appear to be generated by a supported tool "
|
|
956
|
+
"(uv, pip, conda-lock). Proceeding anyway."
|
|
957
|
+
)
|
|
958
|
+
|
|
959
|
+
if conda or pip or lockfile_path:
|
|
960
|
+
senv = await create_env_spec(conda=conda, pip=pip, lockfile_path=lockfile_path)
|
|
948
961
|
if include_local_code:
|
|
949
962
|
prefix = await scan_prefix()
|
|
950
963
|
packages, _ = partition_ignored_packages(
|
|
@@ -1030,6 +1043,8 @@ class Cloud(Generic[IsAsynchronous]):
|
|
|
1030
1043
|
architecture: ArchitectureTypesEnum,
|
|
1031
1044
|
pip_check_errors: Dict[str, List[str]] | None = None,
|
|
1032
1045
|
gpu_enabled: bool = False,
|
|
1046
|
+
lockfile_name: str | None = None,
|
|
1047
|
+
lockfile_content: str | None = None,
|
|
1033
1048
|
) -> List[ApproximatePackageResult]:
|
|
1034
1049
|
response = await self._do_request(
|
|
1035
1050
|
"POST",
|
|
@@ -1046,6 +1061,8 @@ class Cloud(Generic[IsAsynchronous]):
|
|
|
1046
1061
|
"index_urls": get_index_urls(),
|
|
1047
1062
|
"pip_check_errors": pip_check_errors,
|
|
1048
1063
|
"gpu_enabled": gpu_enabled,
|
|
1064
|
+
"lockfile_name": lockfile_name,
|
|
1065
|
+
"lockfile_content": lockfile_content,
|
|
1049
1066
|
},
|
|
1050
1067
|
)
|
|
1051
1068
|
if response.status >= 400:
|
|
@@ -1133,6 +1150,8 @@ class Cloud(Generic[IsAsynchronous]):
|
|
|
1133
1150
|
"architecture": architecture,
|
|
1134
1151
|
"region_name": region_name,
|
|
1135
1152
|
"enable_experimental_installer": use_uv_installer,
|
|
1153
|
+
"lockfile_content": senv.get("lockfile_content") if senv else None,
|
|
1154
|
+
"lockfile_name": senv.get("lockfile_name") if senv else None,
|
|
1136
1155
|
}
|
|
1137
1156
|
if senv:
|
|
1138
1157
|
payload["packages"] = senv["packages"]
|
|
@@ -422,7 +422,7 @@ class FilestoreManagerWithoutHttp:
|
|
|
422
422
|
for chunk in response.iter_bytes(chunk_size=8192):
|
|
423
423
|
f.write(chunk)
|
|
424
424
|
return # Success, exit function
|
|
425
|
-
except (httpx.RemoteProtocolError, httpx.ReadTimeout, httpx.ConnectError) as e:
|
|
425
|
+
except (httpx.RemoteProtocolError, httpx.ReadTimeout, httpx.ConnectError, httpx.HTTPStatusError) as e:
|
|
426
426
|
if attempt < max_retries - 1:
|
|
427
427
|
wait_time = 2**attempt # Exponential backoff: 1s, 2s, 4s
|
|
428
428
|
if verbose:
|