coiled 1.129.3.dev13__tar.gz → 1.130.1.dev14__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

Files changed (104) hide show
  1. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/PKG-INFO +1 -1
  2. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/capture_environment.py +12 -5
  3. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/core.py +21 -2
  4. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/filestore.py +1 -1
  5. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/pypi_conda_map.py +22 -96
  6. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/software.py +18 -3
  7. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/software_utils.py +25 -9
  8. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/types.py +2 -0
  9. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/v2/core.py +11 -1
  10. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/v2/widgets/rich.py +2 -1
  11. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/.gitignore +0 -0
  12. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/LICENSE +0 -0
  13. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/README.md +0 -0
  14. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/__init__.py +0 -0
  15. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/__main__.py +0 -0
  16. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/analytics.py +0 -0
  17. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/auth.py +0 -0
  18. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/batch.py +0 -0
  19. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/__init__.py +0 -0
  20. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/batch/__init__.py +0 -0
  21. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/batch/list.py +0 -0
  22. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/batch/logs.py +0 -0
  23. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/batch/run.py +0 -0
  24. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/batch/status.py +0 -0
  25. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/batch/util.py +0 -0
  26. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/batch/wait.py +0 -0
  27. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/cluster/__init__.py +0 -0
  28. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/cluster/azure_logs.py +0 -0
  29. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/cluster/better_logs.py +0 -0
  30. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/cluster/crud.py +0 -0
  31. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/cluster/get_address.py +0 -0
  32. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/cluster/list.py +0 -0
  33. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/cluster/logs.py +0 -0
  34. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/cluster/metrics.py +0 -0
  35. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/cluster/ssh.py +0 -0
  36. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/cluster/utils.py +0 -0
  37. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/config.py +0 -0
  38. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/core.py +0 -0
  39. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/curl.py +0 -0
  40. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/diagnostics.py +0 -0
  41. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/env.py +0 -0
  42. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/file.py +0 -0
  43. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/hello/__init__.py +0 -0
  44. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/hello/examples/__init__.py +0 -0
  45. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/hello/examples/exit.py +0 -0
  46. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/hello/examples/hello_world.py +0 -0
  47. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/hello/examples/nyc_parquet.py +0 -0
  48. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/hello/examples/pytorch.py +0 -0
  49. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/hello/examples/xarray_nwm.py +0 -0
  50. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/hello/hello.py +0 -0
  51. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/hello/scripts/fill_ipython.py +0 -0
  52. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/hello/scripts/nyc_parquet.py +0 -0
  53. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/hello/scripts/pytorch.py +0 -0
  54. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/hello/scripts/xarray_nwm.py +0 -0
  55. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/hello/utils.py +0 -0
  56. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/login.py +0 -0
  57. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/mpi.py +0 -0
  58. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/notebook/__init__.py +0 -0
  59. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/notebook/notebook.py +0 -0
  60. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/package_sync.py +0 -0
  61. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/prefect.py +0 -0
  62. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/prefect_serve.py +0 -0
  63. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/run.py +0 -0
  64. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/setup/__init__.py +0 -0
  65. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/setup/amp.py +0 -0
  66. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/setup/aws.py +0 -0
  67. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/setup/azure.py +0 -0
  68. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/setup/entry.py +0 -0
  69. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/setup/gcp.py +0 -0
  70. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/setup/prometheus.py +0 -0
  71. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/setup/util.py +0 -0
  72. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/sync.py +0 -0
  73. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/utils.py +0 -0
  74. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cluster.py +0 -0
  75. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/coiled.yaml +0 -0
  76. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/compatibility.py +0 -0
  77. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/config.py +0 -0
  78. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/context.py +0 -0
  79. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/credentials/__init__.py +0 -0
  80. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/credentials/aws.py +0 -0
  81. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/credentials/google.py +0 -0
  82. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/errors.py +0 -0
  83. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/exceptions.py +0 -0
  84. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/extensions/__init__.py +0 -0
  85. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/extensions/prefect/__init__.py +0 -0
  86. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/extensions/prefect/runners.py +0 -0
  87. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/extensions/prefect/workers.py +0 -0
  88. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/function.py +0 -0
  89. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/plugins.py +0 -0
  90. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/prefect.py +0 -0
  91. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/scan.py +0 -0
  92. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/spans.py +0 -0
  93. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/spark.py +0 -0
  94. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/utils.py +0 -0
  95. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/v2/__init__.py +0 -0
  96. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/v2/cluster.py +0 -0
  97. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/v2/cluster_comms.py +0 -0
  98. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/v2/cwi_log_link.py +0 -0
  99. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/v2/states.py +0 -0
  100. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/v2/widgets/__init__.py +0 -0
  101. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/v2/widgets/interface.py +0 -0
  102. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/v2/widgets/util.py +0 -0
  103. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/websockets.py +0 -0
  104. {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/pyproject.toml +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: coiled
3
- Version: 1.129.3.dev13
3
+ Version: 1.130.1.dev14
4
4
  Summary: Python client for coiled.io dask clusters
5
5
  Project-URL: Homepage, https://coiled.io
6
6
  Maintainer-email: Coiled <info@coiled.io>
@@ -14,9 +14,12 @@ from typing_extensions import Literal
14
14
  from coiled.context import track_context
15
15
  from coiled.scan import scan_prefix
16
16
  from coiled.software_utils import (
17
+ ANY_AVAILABLE,
18
+ PYTHON_VERSION,
17
19
  check_pip_happy,
18
20
  create_wheels_for_local_python,
19
21
  create_wheels_for_packages,
22
+ get_lockfile,
20
23
  partition_ignored_packages,
21
24
  partition_local_packages,
22
25
  partition_local_python_code_packages,
@@ -34,10 +37,6 @@ from coiled.v2.core import CloudV2
34
37
  from coiled.v2.widgets.rich import CONSOLE_WIDTH, print_rich_package_table
35
38
  from coiled.v2.widgets.util import simple_progress, use_rich_widget
36
39
 
37
- PYTHON_VERSION = platform.python_version_tuple()
38
- ANY_AVAILABLE = "ANY-AVAILABLE"
39
-
40
-
41
40
  logger = getLogger("coiled.package_sync")
42
41
 
43
42
 
@@ -68,6 +67,7 @@ async def approximate_packages(
68
67
  pip_check_errors: Optional[Dict[str, List[str]]] = None,
69
68
  gpu_enabled: bool = False,
70
69
  use_uv_installer: bool = True,
70
+ lockfile_path: Optional[Path] = None,
71
71
  ) -> typing.List[ResolvedPackageInfo]:
72
72
  user_conda_installed_python = next((p for p in packages if p["name"] == "python"), None)
73
73
  # Only add pip if we need it
@@ -163,6 +163,8 @@ async def approximate_packages(
163
163
  architecture=architecture,
164
164
  pip_check_errors=pip_check_errors,
165
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,
166
168
  )
167
169
  finalized_packages: typing.List[ResolvedPackageInfo] = []
168
170
  finalized_packages.extend(await create_wheels_for_local_python(local_python_code, progress=progress))
@@ -240,6 +242,7 @@ async def create_environment_approximation(
240
242
  pip_check_errors=pip_check_errors,
241
243
  gpu_enabled=gpu_enabled,
242
244
  use_uv_installer=use_uv_installer,
245
+ lockfile_path=get_lockfile(),
243
246
  )
244
247
  return result
245
248
 
@@ -262,7 +265,7 @@ async def scan_and_create(
262
265
  ):
263
266
  use_widget = force_rich_widget or (show_widget and use_rich_widget())
264
267
 
265
- local_env_name = Path(sys.prefix).name
268
+ local_env_name = str(get_lockfile() or Path(sys.prefix).name)
266
269
  if use_widget:
267
270
  progress = Progress(TextColumn("[progress.description]{task.description}"), BarColumn(), TimeElapsedColumn())
268
271
  live = Live(Panel(progress, title=f"[green]Package Sync for {local_env_name}", width=CONSOLE_WIDTH))
@@ -271,6 +274,9 @@ async def scan_and_create(
271
274
  progress = None
272
275
 
273
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
274
280
  with simple_progress("Fetching latest package priorities", progress):
275
281
  logger.info(f"Resolving your local {local_env_name} Python environment...")
276
282
  async with (
@@ -371,6 +377,7 @@ async def scan_and_create(
371
377
  # default region in declarative service create_software_environment
372
378
  region_name=region_name,
373
379
  use_uv_installer=use_uv_installer,
380
+ lockfile_path=get_lockfile(),
374
381
  )
375
382
  if use_widget:
376
383
  print_rich_package_table(packages_with_notes, packages_with_errors)
@@ -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 conda or pip:
947
- senv = await create_env_spec(conda=conda, pip=pip)
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:
@@ -1,58 +1,41 @@
1
1
  # This file was auto-generated by scripts/update_pypi_conda_mapping.py
2
2
  # DO NOT MANUALLY EDIT
3
3
  PYPI_TO_CONDA = {
4
- "Boruta": "boruta_py",
5
- "Flask-Cors": "flask_cors",
6
- "Flask-JSON": "flask_json",
7
- "SoundFile": "pysoundfile",
8
- "aiohttp_cors": "aiohttp-cors",
9
4
  "annoy": "python-annoy",
10
5
  "arrakis": "arrakis-python",
11
6
  "art": "ascii-art",
12
7
  "asana": "python-asana",
13
8
  "backtrace": "python-backtrace",
14
- "bash_completion": "py-bash-completion",
9
+ "bash-completion": "py-bash-completion",
15
10
  "bilby-pipe": "bilby_pipe",
16
11
  "blis": "cython-blis",
12
+ "boruta": "boruta_py",
17
13
  "build": "python-build",
18
14
  "captest": "pvcaptest",
19
15
  "cdo": "python-cdo",
20
- "click_config_file": "click-config-file",
21
16
  "cockroachdb": "cockroachdb-python",
22
- "conda_lock": "conda-lock",
23
- "conda_mirror": "conda-mirror",
24
17
  "confluent-kafka": "python-confluent-kafka",
25
18
  "coreapi": "python-coreapi",
26
19
  "coreschema": "python-coreschema",
27
- "cryptography_vectors": "cryptography-vectors",
28
20
  "cufflinks": "python-cufflinks",
29
21
  "dashing": "python-dashing",
30
22
  "dask": "dask-core",
31
- "data-morph-ai": "data-morph-ai",
32
- "dataclasses": "dataclasses",
33
23
  "datadotworld": "datadotworld-py",
34
- "datalad_container": "datalad-container",
35
- "delegator.py": "delegator",
24
+ "delegator-py": "delegator",
36
25
  "dftd3": "dftd3-python",
37
26
  "dftd4": "dftd4-python",
38
- "dials_data": "dials-data",
39
27
  "docker": "docker-py",
40
28
  "duckdb": "python-duckdb",
41
- "dye_score": "dye-score",
42
29
  "eccodes": "python-eccodes",
43
- "empyrical_dist": "empyrical-dist",
44
- "enum34": "enum34",
45
30
  "esprima": "esprima-python",
46
- "et_xmlfile": "et-xmlfile",
47
31
  "evalml": "evaml-core",
48
- "exceptiongroup": "exceptiongroup",
49
32
  "extract-msg": "msg-extractor",
50
33
  "fastjsonschema": "python-fastjsonschema",
51
34
  "flair": "python-flair",
52
- "flake8_nb": "flake8-nb",
35
+ "flask-cors": "flask_cors",
36
+ "flask-json": "flask_json",
53
37
  "flatbuffers": "python-flatbuffers",
54
38
  "flex": "flex-swagger",
55
- "flit_core": "flit-core",
56
39
  "gnupg": "gnupg-py",
57
40
  "google": "googlesearch",
58
41
  "google-cloud-bigquery": "google-cloud-bigquery-core",
@@ -63,47 +46,32 @@ PYPI_TO_CONDA = {
63
46
  "hdfs": "python-hdfs",
64
47
  "hjson": "hjson-py",
65
48
  "htcondor": "python-htcondor",
66
- "iam_units": "iam-units",
67
- "ib_insync": "ib-insync",
68
- "import_metadata": "import_metadata",
69
- "importlib_metadata": "importlib-metadata",
49
+ "import-metadata": "import_metadata",
70
50
  "installer": "python-installer",
71
51
  "intake-astro": "intake-accumulo",
72
- "intake_geopandas": "intake-geopandas",
73
- "ioos_tools": "ioos-tools",
74
52
  "jsii": "python-jsii",
75
53
  "jupyter-client": "jupyter_client",
76
- "jupyter_jaeger": "jupyter-jaeger",
77
- "jupyterlab_git": "jupyterlab-git",
78
- "jupyterlab_latex": "jupyterlab-latex",
79
54
  "kaleido": "python-kaleido",
80
55
  "kubernetes": "python-kubernetes",
81
56
  "libaio": "python-libaio",
82
57
  "libnacl": "libnacl-python-bindings",
83
58
  "lmdb": "python-lmdb",
84
59
  "matplotlib": "matplotlib-base",
85
- "md_toc": "md-toc",
86
- "message_ix": "message-ix",
87
60
  "msgpack": "msgpack-python",
88
61
  "mss": "python-mss",
89
- "nbconvert_utils": "nbconvert-utils",
90
- "ndarray_listener": "ndarray-listener",
91
62
  "neo": "python-neo",
92
63
  "neo4j": "neo4j-python-driver",
93
- "nest_asyncio": "nest-asyncio",
94
64
  "node-semver": "python-node-semver",
95
65
  "nvidia-ml-py3": "nvidia-ml",
96
66
  "opencv-python": "opencv",
97
67
  "opencv-python-headless": "opencv",
98
68
  "ortools": "ortools-python",
99
69
  "p-astro": "p_astro",
100
- "pandas_flavor": "pandas-flavor",
101
70
  "paragraph": "python-paragraph",
102
71
  "prince": "prince-factor-analysis",
103
72
  "pvlib": "pvlib-python",
104
73
  "pyqt4": "pyqt",
105
74
  "pyqt5": "pyqt",
106
- "pytest_check_links": "pytest-check-links",
107
75
  "python-datamatrix": "datamatrix",
108
76
  "python-fileinspector": "fileinspector",
109
77
  "python-opencv": "opencv",
@@ -113,16 +81,12 @@ PYPI_TO_CONDA = {
113
81
  "python-qnotifications": "qnotifications",
114
82
  "pywin32": "pywin32-on-windows",
115
83
  "quantum-grove": "grove",
116
- "radio_beam": "radio-beam",
117
84
  "redis": "redis-py",
118
85
  "rocketpyalpha": "rocketpy",
119
- "sage_flatsurf": "sage-flatsurf",
120
- "sagemaker_inference": "sagemaker-inference-toolkit",
121
- "sagemaker_mxnet_training": "sagemaker_mxnet_container",
122
- "scikit-build": "scikit-build",
123
- "scikit-learn": "scikit-learn",
124
- "setuptools_scm": "setuptools-scm",
86
+ "sagemaker-inference": "sagemaker-inference-toolkit",
87
+ "sagemaker-mxnet-training": "sagemaker_mxnet_container",
125
88
  "sounddevice": "python-sounddevice",
89
+ "soundfile": "pysoundfile",
126
90
  "spherical-functions": "spherical_functions",
127
91
  "sphinx-rtd-theme": "sphinx_rtd_theme",
128
92
  "stjudecloud-oliver": "oliver",
@@ -133,54 +97,37 @@ PYPI_TO_CONDA = {
133
97
  "termstyle": "python-termstyle",
134
98
  "torch": "pytorch",
135
99
  "torch-cluster": "pytorch_cluster",
100
+ "torch-geometric": "pytorch_geometric",
136
101
  "torch-sparse": "pytorch_sparse",
137
- "torch_geometric": "pytorch_geometric",
138
102
  "trino": "trino-python-client",
139
- "typing": "typing",
140
103
  "typing-extensions": "typing_extensions",
141
- "useDAVE": "dave",
104
+ "usedave": "dave",
142
105
  "vsts": "vsts-python-api",
143
106
  "xtb": "xtb-python",
144
107
  "xxhash": "python-xxhash",
145
108
  }
146
109
 
147
110
  CONDA_TO_PYPI = {
148
- "aiohttp-cors": "aiohttp_cors",
149
111
  "arrakis-python": "arrakis",
150
112
  "ascii-art": "art",
151
113
  "bilby_pipe": "bilby-pipe",
152
- "boruta_py": "Boruta",
153
- "click-config-file": "click_config_file",
114
+ "boruta_py": "boruta",
154
115
  "cockroachdb-python": "cockroachdb",
155
- "conda-lock": "conda_lock",
156
- "conda-mirror": "conda_mirror",
157
- "cryptography-vectors": "cryptography_vectors",
158
116
  "cython-blis": "blis",
159
117
  "dask-core": "dask",
160
- "data-morph-ai": "data-morph-ai",
161
- "dataclasses": "dataclasses",
162
118
  "datadotworld-py": "datadotworld",
163
- "datalad-container": "datalad_container",
164
119
  "datamatrix": "python-datamatrix",
165
- "dave": "useDAVE",
166
- "delegator": "delegator.py",
120
+ "dave": "usedave",
121
+ "delegator": "delegator-py",
167
122
  "dftd3-python": "dftd3",
168
123
  "dftd4-python": "dftd4",
169
- "dials-data": "dials_data",
170
124
  "docker-py": "docker",
171
- "dye-score": "dye_score",
172
- "empyrical-dist": "empyrical_dist",
173
- "enum34": "enum34",
174
125
  "esprima-python": "esprima",
175
- "et-xmlfile": "et_xmlfile",
176
126
  "evaml-core": "evalml",
177
- "exceptiongroup": "exceptiongroup",
178
127
  "fileinspector": "python-fileinspector",
179
- "flake8-nb": "flake8_nb",
180
- "flask_cors": "Flask-Cors",
181
- "flask_json": "Flask-JSON",
128
+ "flask_cors": "flask-cors",
129
+ "flask_json": "flask-json",
182
130
  "flex-swagger": "flex",
183
- "flit-core": "flit_core",
184
131
  "gnupg-py": "gnupg",
185
132
  "google-cloud-bigquery-core": "google-cloud-bigquery",
186
133
  "googlesearch": "google",
@@ -188,43 +135,28 @@ CONDA_TO_PYPI = {
188
135
  "h2o-py": "h2o",
189
136
  "h3-py": "h3",
190
137
  "hjson-py": "hjson",
191
- "iam-units": "iam_units",
192
- "ib-insync": "ib_insync",
193
- "import_metadata": "import_metadata",
194
- "importlib-metadata": "importlib_metadata",
138
+ "import_metadata": "import-metadata",
195
139
  "intake-accumulo": "intake-astro",
196
- "intake-geopandas": "intake_geopandas",
197
- "ioos-tools": "ioos_tools",
198
- "jupyter-jaeger": "jupyter_jaeger",
199
140
  "jupyter_client": "jupyter-client",
200
- "jupyterlab-git": "jupyterlab_git",
201
- "jupyterlab-latex": "jupyterlab_latex",
202
141
  "libnacl-python-bindings": "libnacl",
203
142
  "matplotlib-base": "matplotlib",
204
- "md-toc": "md_toc",
205
- "message-ix": "message_ix",
206
143
  "msg-extractor": "extract-msg",
207
144
  "msgpack-python": "msgpack",
208
- "nbconvert-utils": "nbconvert_utils",
209
- "ndarray-listener": "ndarray_listener",
210
145
  "neo4j-python-driver": "neo4j",
211
- "nest-asyncio": "nest_asyncio",
212
146
  "nvidia-ml": "nvidia-ml-py3",
213
147
  "oliver": "stjudecloud-oliver",
214
148
  "opencv": "python-opencv",
215
149
  "ortools-python": "ortools",
216
150
  "p_astro": "p-astro",
217
- "pandas-flavor": "pandas_flavor",
218
151
  "prince-factor-analysis": "prince",
219
152
  "pseudorandom": "python-pseudorandom",
220
153
  "pvcaptest": "captest",
221
154
  "pvlib-python": "pvlib",
222
- "py-bash-completion": "bash_completion",
155
+ "py-bash-completion": "bash-completion",
223
156
  "pygaze": "python-pygaze",
224
157
  "pyqt": "pyqt5",
225
- "pysoundfile": "SoundFile",
158
+ "pysoundfile": "soundfile",
226
159
  "pytables": "tables",
227
- "pytest-check-links": "pytest_check_links",
228
160
  "python-annoy": "annoy",
229
161
  "python-asana": "asana",
230
162
  "python-backtrace": "backtrace",
@@ -261,25 +193,19 @@ CONDA_TO_PYPI = {
261
193
  "python-xxhash": "xxhash",
262
194
  "pytorch": "torch",
263
195
  "pytorch_cluster": "torch-cluster",
264
- "pytorch_geometric": "torch_geometric",
196
+ "pytorch_geometric": "torch-geometric",
265
197
  "pytorch_sparse": "torch-sparse",
266
198
  "pywin32-on-windows": "pywin32",
267
199
  "qdatamatrix": "python-qdatamatrix",
268
200
  "qnotifications": "python-qnotifications",
269
- "radio-beam": "radio_beam",
270
201
  "redis-py": "redis",
271
202
  "rocketpy": "rocketpyalpha",
272
- "sage-flatsurf": "sage_flatsurf",
273
- "sagemaker-inference-toolkit": "sagemaker_inference",
274
- "sagemaker_mxnet_container": "sagemaker_mxnet_training",
275
- "scikit-build": "scikit-build",
276
- "scikit-learn": "scikit-learn",
277
- "setuptools-scm": "setuptools_scm",
203
+ "sagemaker-inference-toolkit": "sagemaker-inference",
204
+ "sagemaker_mxnet_container": "sagemaker-mxnet-training",
278
205
  "spherical_functions": "spherical-functions",
279
206
  "sphinx_rtd_theme": "sphinx-rtd-theme",
280
207
  "tblite-python": "tblite",
281
208
  "trino-python-client": "trino",
282
- "typing": "typing",
283
209
  "typing_extensions": "typing-extensions",
284
210
  "vsts-python-api": "vsts",
285
211
  "xtb-python": "xtb",
@@ -118,6 +118,9 @@ def parse_pip(pip: Union[List[str], str, Path]) -> Tuple[List[PackageSchema], Li
118
118
  if req["is_editable"]:
119
119
  logger.warning(f"Editable requirement {raw_line!r} is not supported and will be ignored")
120
120
  continue
121
+ if req.get("is_local_path", False):
122
+ logger.warning(f"Local path requirement {raw_line!r} is not supported and will be ignored")
123
+ continue
121
124
  if req["is_vcs_url"]:
122
125
  raw_pip.append(raw_line)
123
126
  continue
@@ -139,10 +142,22 @@ def parse_pip(pip: Union[List[str], str, Path]) -> Tuple[List[PackageSchema], Li
139
142
  async def create_env_spec(
140
143
  conda: Union[CondaEnvSchema, str, Path, list, None] = None,
141
144
  pip: Union[List[str], str, Path, None] = None,
145
+ lockfile_path: Union[str, Path, None] = None,
142
146
  ) -> SoftwareEnvSpec:
143
- if not conda and not pip:
144
- raise TypeError("Either or both of conda/pip kwargs must be specified")
145
- spec: SoftwareEnvSpec = {"packages": [], "raw_conda": None, "raw_pip": None}
147
+ if not conda and not pip and not lockfile_path:
148
+ raise TypeError("At least one of the conda, pip, and lockfile_path kwargs must be specified")
149
+ spec: SoftwareEnvSpec = {
150
+ "packages": [],
151
+ "raw_conda": None,
152
+ "raw_pip": None,
153
+ "lockfile_name": None,
154
+ "lockfile_content": None,
155
+ }
156
+ if lockfile_path:
157
+ lockfile_path = Path(lockfile_path)
158
+ lockfile_content = lockfile_path.read_text()
159
+ spec["lockfile_name"] = lockfile_path.name
160
+ spec["lockfile_content"] = lockfile_content
146
161
  if conda:
147
162
  packages, raw_conda, raw_pip = parse_conda(conda)
148
163
  spec["raw_conda"] = raw_conda
@@ -477,11 +477,10 @@ async def check_pip_happy(progress: Progress | None = None) -> Dict[str, List[st
477
477
  return faulty_packages
478
478
 
479
479
 
480
- def _load_toml(path: Union[Path, str]) -> Dict:
480
+ def _find_file_in_hierarchy(path: Union[Path, str]) -> Path | None:
481
481
  path = Path(path)
482
482
  filename = path.name
483
483
  dir_path = safe_path_resolve(path.parent)
484
- toml_dict = {}
485
484
  # Walk up the directory tree to find the first toml file (up to 10 levels above)
486
485
  for _ in range(10):
487
486
  path = dir_path / filename
@@ -490,13 +489,21 @@ def _load_toml(path: Union[Path, str]) -> Dict:
490
489
  break
491
490
  dir_path = dir_path.parent
492
491
  continue
493
- try:
494
- with path.open() as f:
495
- toml_dict = toml.load(f)
496
- except (toml.TomlDecodeError, FileNotFoundError, IndexError) as e:
497
- logger.debug(f"Failed to load {path}: {e}", exc_info=True)
498
- break
499
- return toml_dict
492
+ return path
493
+ return None
494
+
495
+
496
+ def _load_toml(path: Union[Path, str]) -> Dict:
497
+ resolved_path = _find_file_in_hierarchy(path)
498
+ if not resolved_path:
499
+ logger.debug(f"Failed to find {path}")
500
+ return {}
501
+ try:
502
+ with resolved_path.open() as f:
503
+ return toml.load(f)
504
+ except Exception as e:
505
+ logger.debug(f"Failed to load {path}: {e}", exc_info=True)
506
+ return {}
500
507
 
501
508
 
502
509
  def _get_pip_index_urls() -> List[str]:
@@ -1041,3 +1048,12 @@ def make_coiled_local_name(dirname: str):
1041
1048
  if not cleaned_name:
1042
1049
  cleaned_name = "rootdir"
1043
1050
  return COILED_LOCAL_PACKAGE_PREFIX + cleaned_name
1051
+
1052
+
1053
+ def get_lockfile() -> Path | None:
1054
+ """Search for a supported lockfile and return its path"""
1055
+ # TODO: Add support for other lockfiles
1056
+ lockfile = None
1057
+ if dask.config.get("coiled.package_sync.use_lockfile.uv", True):
1058
+ lockfile = _find_file_in_hierarchy("uv.lock")
1059
+ return lockfile
@@ -154,6 +154,8 @@ class SoftwareEnvSpec(TypedDict):
154
154
  packages: List[PackageSchema]
155
155
  raw_pip: Optional[List[str]]
156
156
  raw_conda: Optional[CondaEnvSchema]
157
+ lockfile_name: Optional[str]
158
+ lockfile_content: Optional[str]
157
159
 
158
160
 
159
161
  # KNOWN_SUBDIRS is copied from conda's known subdirs
@@ -7,6 +7,7 @@ import logging
7
7
  import time
8
8
  import weakref
9
9
  from collections import namedtuple
10
+ from pathlib import Path
10
11
  from typing import (
11
12
  Awaitable,
12
13
  Callable,
@@ -523,6 +524,7 @@ class CloudV2(OldCloud, Generic[IsAsynchronous]):
523
524
  architecture: ArchitectureTypesEnum = ArchitectureTypesEnum.X86_64,
524
525
  region_name: str | None = None,
525
526
  use_uv_installer: bool = True,
527
+ lockfile_path: str | Path | None = None,
526
528
  ) -> SoftwareEnvironmentAlias:
527
529
  workspace = workspace or self.default_workspace
528
530
  prepared_packages: List[PackageSchema] = []
@@ -547,12 +549,20 @@ class CloudV2(OldCloud, Generic[IsAsynchronous]):
547
549
  "client_version": pkg["client_version"],
548
550
  "file": file_id,
549
551
  })
550
- with simple_progress("Requesting package sync build", progress=progress):
552
+ lockfile_content = None
553
+ if lockfile_path:
554
+ lockfile_path = Path(lockfile_path)
555
+ lockfile_content = lockfile_path.read_text()
556
+ with simple_progress(
557
+ "Requesting package sync build" if not lockfile_path else "Creating software environment", progress=progress
558
+ ):
551
559
  result = await self._create_software_environment_v2(
552
560
  senv={
553
561
  "packages": prepared_packages,
554
562
  "raw_pip": None,
555
563
  "raw_conda": None,
564
+ "lockfile_name": str(lockfile_path.name) if lockfile_path else None,
565
+ "lockfile_content": lockfile_content,
556
566
  },
557
567
  workspace=workspace,
558
568
  architecture=architecture,
@@ -22,6 +22,7 @@ import rich.progress
22
22
  import rich.table
23
23
  from rich.console import RenderableType
24
24
 
25
+ from coiled.software_utils import get_lockfile
25
26
  from coiled.types import PackageLevelEnum
26
27
 
27
28
  from ...capture_environment import ResolvedPackageInfo
@@ -289,7 +290,7 @@ class LightRichClusterWidget(ClusterWidget):
289
290
  env_name = self._cluster_details["senv_alias"]["name"]
290
291
  if env_name.startswith("package-sync-"):
291
292
  env_name = ""
292
- local_env_name = Path(sys.prefix).name
293
+ local_env_name = str(get_lockfile() or Path(sys.prefix).name)
293
294
  env_line = f"[bold green]Synced local Python environment:[/bold green] {local_env_name}"
294
295
  else:
295
296
  local_env_name = ""
File without changes
File without changes