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.
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/PKG-INFO +1 -1
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/capture_environment.py +12 -5
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/core.py +21 -2
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/filestore.py +1 -1
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/pypi_conda_map.py +22 -96
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/software.py +18 -3
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/software_utils.py +25 -9
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/types.py +2 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/v2/core.py +11 -1
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/v2/widgets/rich.py +2 -1
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/.gitignore +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/LICENSE +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/README.md +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/__init__.py +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/__main__.py +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/analytics.py +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/auth.py +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/batch.py +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/__init__.py +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/batch/__init__.py +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/batch/list.py +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/batch/logs.py +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/batch/run.py +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/batch/status.py +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/batch/util.py +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/batch/wait.py +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/cluster/__init__.py +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/cluster/azure_logs.py +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/cluster/better_logs.py +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/cluster/crud.py +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/cluster/get_address.py +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/cluster/list.py +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/cluster/logs.py +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/cluster/metrics.py +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/cluster/ssh.py +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/cluster/utils.py +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/config.py +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/core.py +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/curl.py +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/diagnostics.py +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/env.py +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/file.py +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/hello/__init__.py +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/hello/examples/__init__.py +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/hello/examples/exit.py +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/hello/examples/hello_world.py +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/hello/examples/nyc_parquet.py +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/hello/examples/pytorch.py +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/hello/examples/xarray_nwm.py +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/hello/hello.py +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/hello/scripts/fill_ipython.py +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/hello/scripts/nyc_parquet.py +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/hello/scripts/pytorch.py +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/hello/scripts/xarray_nwm.py +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/hello/utils.py +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/login.py +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/mpi.py +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/notebook/__init__.py +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/notebook/notebook.py +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/package_sync.py +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/prefect.py +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/prefect_serve.py +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/run.py +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/setup/__init__.py +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/setup/amp.py +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/setup/aws.py +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/setup/azure.py +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/setup/entry.py +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/setup/gcp.py +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/setup/prometheus.py +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/setup/util.py +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/sync.py +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cli/utils.py +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/cluster.py +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/coiled.yaml +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/compatibility.py +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/config.py +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/context.py +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/credentials/__init__.py +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/credentials/aws.py +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/credentials/google.py +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/errors.py +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/exceptions.py +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/extensions/__init__.py +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/extensions/prefect/__init__.py +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/extensions/prefect/runners.py +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/extensions/prefect/workers.py +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/function.py +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/plugins.py +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/prefect.py +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/scan.py +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/spans.py +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/spark.py +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/utils.py +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/v2/__init__.py +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/v2/cluster.py +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/v2/cluster_comms.py +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/v2/cwi_log_link.py +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/v2/states.py +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/v2/widgets/__init__.py +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/v2/widgets/interface.py +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/v2/widgets/util.py +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/coiled/websockets.py +0 -0
- {coiled-1.129.3.dev13 → coiled-1.130.1.dev14}/pyproject.toml +0 -0
|
@@ -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
|
|
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:
|
|
@@ -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
|
-
"
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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
|
-
"
|
|
120
|
-
"
|
|
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
|
-
"
|
|
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": "
|
|
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": "
|
|
166
|
-
"delegator": "delegator
|
|
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
|
-
"
|
|
180
|
-
"
|
|
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
|
-
"
|
|
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": "
|
|
155
|
+
"py-bash-completion": "bash-completion",
|
|
223
156
|
"pygaze": "python-pygaze",
|
|
224
157
|
"pyqt": "pyqt5",
|
|
225
|
-
"pysoundfile": "
|
|
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": "
|
|
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
|
-
"
|
|
273
|
-
"
|
|
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("
|
|
145
|
-
spec: SoftwareEnvSpec = {
|
|
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
|
|
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
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
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
|
-
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|