metaflow-netflixext 1.3.2.dev0__tar.gz → 1.3.4.dev0__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.
- {metaflow_netflixext-1.3.2.dev0/metaflow_netflixext.egg-info → metaflow_netflixext-1.3.4.dev0}/PKG-INFO +1 -1
- {metaflow_netflixext-1.3.2.dev0 → metaflow_netflixext-1.3.4.dev0}/metaflow_extensions/netflix_ext/plugins/conda/conda.py +23 -1
- {metaflow_netflixext-1.3.2.dev0 → metaflow_netflixext-1.3.4.dev0}/metaflow_extensions/netflix_ext/plugins/conda/conda_common_decorator.py +21 -0
- {metaflow_netflixext-1.3.2.dev0 → metaflow_netflixext-1.3.4.dev0}/metaflow_extensions/netflix_ext/plugins/conda/conda_environment.py +6 -1
- {metaflow_netflixext-1.3.2.dev0 → metaflow_netflixext-1.3.4.dev0}/metaflow_extensions/netflix_ext/plugins/conda/conda_flow_mutator.py +30 -7
- {metaflow_netflixext-1.3.2.dev0 → metaflow_netflixext-1.3.4.dev0}/metaflow_extensions/netflix_ext/plugins/conda/conda_step_decorator.py +14 -0
- {metaflow_netflixext-1.3.2.dev0 → metaflow_netflixext-1.3.4.dev0}/metaflow_extensions/netflix_ext/plugins/conda/env_descr.py +70 -12
- {metaflow_netflixext-1.3.2.dev0 → metaflow_netflixext-1.3.4.dev0}/metaflow_extensions/netflix_ext/plugins/conda/envsresolver.py +10 -1
- {metaflow_netflixext-1.3.2.dev0 → metaflow_netflixext-1.3.4.dev0}/metaflow_extensions/netflix_ext/plugins/conda/resolvers/conda_resolver.py +6 -0
- {metaflow_netflixext-1.3.2.dev0 → metaflow_netflixext-1.3.4.dev0}/metaflow_extensions/netflix_ext/plugins/conda/resolvers/pip_resolver.py +5 -0
- {metaflow_netflixext-1.3.2.dev0 → metaflow_netflixext-1.3.4.dev0}/metaflow_extensions/netflix_ext/plugins/conda/resolvers/pylock_toml_resolver.py +1 -1
- {metaflow_netflixext-1.3.2.dev0 → metaflow_netflixext-1.3.4.dev0}/metaflow_extensions/netflix_ext/plugins/conda/utils.py +12 -0
- {metaflow_netflixext-1.3.2.dev0 → metaflow_netflixext-1.3.4.dev0/metaflow_netflixext.egg-info}/PKG-INFO +1 -1
- {metaflow_netflixext-1.3.2.dev0 → metaflow_netflixext-1.3.4.dev0}/LICENSE +0 -0
- {metaflow_netflixext-1.3.2.dev0 → metaflow_netflixext-1.3.4.dev0}/README.md +0 -0
- {metaflow_netflixext-1.3.2.dev0 → metaflow_netflixext-1.3.4.dev0}/metaflow_extensions/netflix_ext/__init__.py +0 -0
- {metaflow_netflixext-1.3.2.dev0 → metaflow_netflixext-1.3.4.dev0}/metaflow_extensions/netflix_ext/cmd/debug/__init__.py +0 -0
- {metaflow_netflixext-1.3.2.dev0 → metaflow_netflixext-1.3.4.dev0}/metaflow_extensions/netflix_ext/cmd/debug/constants.py +0 -0
- {metaflow_netflixext-1.3.2.dev0 → metaflow_netflixext-1.3.4.dev0}/metaflow_extensions/netflix_ext/cmd/debug/current_stub_generator.py +0 -0
- {metaflow_netflixext-1.3.2.dev0 → metaflow_netflixext-1.3.4.dev0}/metaflow_extensions/netflix_ext/cmd/debug/debug_cmd.py +0 -0
- {metaflow_netflixext-1.3.2.dev0 → metaflow_netflixext-1.3.4.dev0}/metaflow_extensions/netflix_ext/cmd/debug/debug_script_generator.py +0 -0
- {metaflow_netflixext-1.3.2.dev0 → metaflow_netflixext-1.3.4.dev0}/metaflow_extensions/netflix_ext/cmd/debug/debug_stub_generator.py +0 -0
- {metaflow_netflixext-1.3.2.dev0 → metaflow_netflixext-1.3.4.dev0}/metaflow_extensions/netflix_ext/cmd/debug/debug_utils.py +0 -0
- {metaflow_netflixext-1.3.2.dev0 → metaflow_netflixext-1.3.4.dev0}/metaflow_extensions/netflix_ext/cmd/debug/jupyter_instructions_markdown.py +0 -0
- {metaflow_netflixext-1.3.2.dev0 → metaflow_netflixext-1.3.4.dev0}/metaflow_extensions/netflix_ext/cmd/debug/jupyter_title_markdown.py +0 -0
- {metaflow_netflixext-1.3.2.dev0 → metaflow_netflixext-1.3.4.dev0}/metaflow_extensions/netflix_ext/cmd/environment/__init__.py +0 -0
- {metaflow_netflixext-1.3.2.dev0 → metaflow_netflixext-1.3.4.dev0}/metaflow_extensions/netflix_ext/cmd/environment/environment_cmd.py +0 -0
- {metaflow_netflixext-1.3.2.dev0 → metaflow_netflixext-1.3.4.dev0}/metaflow_extensions/netflix_ext/cmd/environment/utils.py +0 -0
- {metaflow_netflixext-1.3.2.dev0 → metaflow_netflixext-1.3.4.dev0}/metaflow_extensions/netflix_ext/cmd/mfextinit_netflixext.py +0 -0
- {metaflow_netflixext-1.3.2.dev0 → metaflow_netflixext-1.3.4.dev0}/metaflow_extensions/netflix_ext/config/mfextinit_netflixext.py +0 -0
- {metaflow_netflixext-1.3.2.dev0 → metaflow_netflixext-1.3.4.dev0}/metaflow_extensions/netflix_ext/exceptions/__init__.py +0 -0
- {metaflow_netflixext-1.3.2.dev0 → metaflow_netflixext-1.3.4.dev0}/metaflow_extensions/netflix_ext/exceptions/decorators.py +0 -0
- {metaflow_netflixext-1.3.2.dev0 → metaflow_netflixext-1.3.4.dev0}/metaflow_extensions/netflix_ext/exceptions/http_helpers.py +0 -0
- {metaflow_netflixext-1.3.2.dev0 → metaflow_netflixext-1.3.4.dev0}/metaflow_extensions/netflix_ext/generate_vendor.py +0 -0
- {metaflow_netflixext-1.3.2.dev0 → metaflow_netflixext-1.3.4.dev0}/metaflow_extensions/netflix_ext/http_helpers.py +0 -0
- {metaflow_netflixext-1.3.2.dev0 → metaflow_netflixext-1.3.4.dev0}/metaflow_extensions/netflix_ext/plugins/conda/__init__.py +0 -0
- {metaflow_netflixext-1.3.2.dev0 → metaflow_netflixext-1.3.4.dev0}/metaflow_extensions/netflix_ext/plugins/conda/conda_flow_decorator.py +0 -0
- {metaflow_netflixext-1.3.2.dev0 → metaflow_netflixext-1.3.4.dev0}/metaflow_extensions/netflix_ext/plugins/conda/conda_lock_micromamba_server.py +0 -0
- {metaflow_netflixext-1.3.2.dev0 → metaflow_netflixext-1.3.4.dev0}/metaflow_extensions/netflix_ext/plugins/conda/parsers.py +0 -0
- {metaflow_netflixext-1.3.2.dev0 → metaflow_netflixext-1.3.4.dev0}/metaflow_extensions/netflix_ext/plugins/conda/pypi_package_builder.py +0 -0
- {metaflow_netflixext-1.3.2.dev0 → metaflow_netflixext-1.3.4.dev0}/metaflow_extensions/netflix_ext/plugins/conda/remote_bootstrap.py +0 -0
- {metaflow_netflixext-1.3.2.dev0 → metaflow_netflixext-1.3.4.dev0}/metaflow_extensions/netflix_ext/plugins/conda/resolvers/__init__.py +0 -0
- {metaflow_netflixext-1.3.2.dev0 → metaflow_netflixext-1.3.4.dev0}/metaflow_extensions/netflix_ext/plugins/conda/resolvers/builder_envs_resolver.py +0 -0
- {metaflow_netflixext-1.3.2.dev0 → metaflow_netflixext-1.3.4.dev0}/metaflow_extensions/netflix_ext/plugins/conda/resolvers/conda_lock_resolver.py +0 -0
- {metaflow_netflixext-1.3.2.dev0 → metaflow_netflixext-1.3.4.dev0}/metaflow_extensions/netflix_ext/plugins/conda/resolvers/micromamba_server_resolver.py +0 -0
- {metaflow_netflixext-1.3.2.dev0 → metaflow_netflixext-1.3.4.dev0}/metaflow_extensions/netflix_ext/plugins/conda/resources/logo-32x32.png +0 -0
- {metaflow_netflixext-1.3.2.dev0 → metaflow_netflixext-1.3.4.dev0}/metaflow_extensions/netflix_ext/plugins/conda/resources/logo-64x64.png +0 -0
- {metaflow_netflixext-1.3.2.dev0 → metaflow_netflixext-1.3.4.dev0}/metaflow_extensions/netflix_ext/plugins/conda/resources/logo-svg.svg +0 -0
- {metaflow_netflixext-1.3.2.dev0 → metaflow_netflixext-1.3.4.dev0}/metaflow_extensions/netflix_ext/plugins/conda/terminal_menu.py +0 -0
- {metaflow_netflixext-1.3.2.dev0 → metaflow_netflixext-1.3.4.dev0}/metaflow_extensions/netflix_ext/plugins/environment_cli.py +0 -0
- {metaflow_netflixext-1.3.2.dev0 → metaflow_netflixext-1.3.4.dev0}/metaflow_extensions/netflix_ext/plugins/mfextinit_netflixext.py +0 -0
- {metaflow_netflixext-1.3.2.dev0 → metaflow_netflixext-1.3.4.dev0}/metaflow_extensions/netflix_ext/toplevel/mfextinit_netflixext.py +0 -0
- {metaflow_netflixext-1.3.2.dev0 → metaflow_netflixext-1.3.4.dev0}/metaflow_extensions/netflix_ext/toplevel/netflixext_toplevel.py +0 -0
- {metaflow_netflixext-1.3.2.dev0 → metaflow_netflixext-1.3.4.dev0}/metaflow_extensions/netflix_ext/toplevel/netflixext_version.py +0 -0
- {metaflow_netflixext-1.3.2.dev0 → metaflow_netflixext-1.3.4.dev0}/metaflow_netflixext.egg-info/SOURCES.txt +0 -0
- {metaflow_netflixext-1.3.2.dev0 → metaflow_netflixext-1.3.4.dev0}/metaflow_netflixext.egg-info/dependency_links.txt +0 -0
- {metaflow_netflixext-1.3.2.dev0 → metaflow_netflixext-1.3.4.dev0}/metaflow_netflixext.egg-info/requires.txt +0 -0
- {metaflow_netflixext-1.3.2.dev0 → metaflow_netflixext-1.3.4.dev0}/metaflow_netflixext.egg-info/top_level.txt +0 -0
- {metaflow_netflixext-1.3.2.dev0 → metaflow_netflixext-1.3.4.dev0}/setup.cfg +0 -0
- {metaflow_netflixext-1.3.2.dev0 → metaflow_netflixext-1.3.4.dev0}/setup.py +0 -0
|
@@ -685,6 +685,7 @@ class Conda(object):
|
|
|
685
685
|
env_id: EnvID,
|
|
686
686
|
local_only: bool = False,
|
|
687
687
|
use_latest: str = CONDA_USE_REMOTE_LATEST,
|
|
688
|
+
full_id_unique_keys: Optional[Dict[str, str]] = None,
|
|
688
689
|
) -> Optional[ResolvedEnvironment]:
|
|
689
690
|
"""
|
|
690
691
|
Returns the resolved environment for a given environment ID.
|
|
@@ -712,7 +713,9 @@ class Conda(object):
|
|
|
712
713
|
The ResolvedEnvironment corresponding to the input EnvID
|
|
713
714
|
"""
|
|
714
715
|
# First look if we have from_env_id locally
|
|
715
|
-
env = self._cached_environment.env_for(
|
|
716
|
+
env = self._cached_environment.env_for(
|
|
717
|
+
*env_id, full_id_unique_keys=full_id_unique_keys
|
|
718
|
+
)
|
|
716
719
|
|
|
717
720
|
debug.conda_exec("%s%sfound locally" % (str(env_id), " " if env else " not "))
|
|
718
721
|
if env:
|
|
@@ -760,6 +763,25 @@ class Conda(object):
|
|
|
760
763
|
"%s found as latest remotely: %s"
|
|
761
764
|
% (str(env_id), str(env.env_id))
|
|
762
765
|
)
|
|
766
|
+
|
|
767
|
+
# In case the resolved files changed but the user dependency stays the same,
|
|
768
|
+
# which happens when pyproject.toml stays the same but the user does
|
|
769
|
+
# `uv lock --upgrade` -- the full_id_unique_keys which includes information
|
|
770
|
+
# from the resolved files hash will be different, and the cache environment
|
|
771
|
+
# will be invalidated.
|
|
772
|
+
if full_id_unique_keys and env:
|
|
773
|
+
if env._full_id_unique_keys != full_id_unique_keys:
|
|
774
|
+
debug.conda_exec(
|
|
775
|
+
"%s found remotely but unique keys do not match, unique key in cache is %s"
|
|
776
|
+
" unique key in request is %s"
|
|
777
|
+
% (
|
|
778
|
+
str(env.env_id),
|
|
779
|
+
str(env._full_id_unique_keys),
|
|
780
|
+
str(full_id_unique_keys),
|
|
781
|
+
)
|
|
782
|
+
)
|
|
783
|
+
return None
|
|
784
|
+
|
|
763
785
|
return env
|
|
764
786
|
|
|
765
787
|
def environments(
|
|
@@ -45,6 +45,10 @@ class StepRequirementIface:
|
|
|
45
45
|
def file_paths(self) -> Dict[str, List[str]]:
|
|
46
46
|
return {}
|
|
47
47
|
|
|
48
|
+
@property
|
|
49
|
+
def full_id_unique_keys(self) -> Dict[str, str]:
|
|
50
|
+
return {}
|
|
51
|
+
|
|
48
52
|
@property
|
|
49
53
|
def extras(self) -> Dict[str, List[str]]:
|
|
50
54
|
return {}
|
|
@@ -102,6 +106,7 @@ class StepRequirement(StepRequirementIface):
|
|
|
102
106
|
self._sources = {} # type: Dict[str, List[str]]
|
|
103
107
|
self._file_paths = {} # type: Dict[str, List[str]]
|
|
104
108
|
self._extras = {} # type: Dict[str, List[str]]
|
|
109
|
+
self._full_id_unique_keys = {} # type: Dict[str, str]
|
|
105
110
|
self._default_disabled = {
|
|
106
111
|
UBF_CONTROL: None,
|
|
107
112
|
UBF_TASK: None,
|
|
@@ -117,6 +122,7 @@ class StepRequirement(StepRequirementIface):
|
|
|
117
122
|
n._packages = copy.deepcopy(self._packages)
|
|
118
123
|
n._sources = copy.deepcopy(self._sources)
|
|
119
124
|
n._file_paths = copy.deepcopy(self._file_paths)
|
|
125
|
+
n._full_id_unique_keys = copy.deepcopy(self._full_id_unique_keys)
|
|
120
126
|
n._extras = copy.deepcopy(self._extras)
|
|
121
127
|
n._default_disabled = copy.deepcopy(self._default_disabled)
|
|
122
128
|
return n
|
|
@@ -195,6 +201,14 @@ class StepRequirement(StepRequirementIface):
|
|
|
195
201
|
def file_paths(self, value: Dict[str, List[str]]):
|
|
196
202
|
self._file_paths = value
|
|
197
203
|
|
|
204
|
+
@property
|
|
205
|
+
def full_id_unique_keys(self) -> Dict[str, str]:
|
|
206
|
+
return copy.deepcopy(self._full_id_unique_keys)
|
|
207
|
+
|
|
208
|
+
@full_id_unique_keys.setter
|
|
209
|
+
def full_id_unique_keys(self, value: Dict[str, str]):
|
|
210
|
+
self._full_id_unique_keys = value
|
|
211
|
+
|
|
198
212
|
@property
|
|
199
213
|
def extras(self) -> Dict[str, List[str]]:
|
|
200
214
|
return copy.deepcopy(self._extras)
|
|
@@ -305,6 +319,13 @@ class StepRequirement(StepRequirementIface):
|
|
|
305
319
|
for category, extras in other_extras.items():
|
|
306
320
|
self._extras.setdefault(category, []).extend(extras or [])
|
|
307
321
|
|
|
322
|
+
other_full_id_unique_keys = other.full_id_unique_keys
|
|
323
|
+
for key, value in other_full_id_unique_keys.items():
|
|
324
|
+
# We use override rather than list extension here for same keys,
|
|
325
|
+
# as we assume it rare that different decorators will have same
|
|
326
|
+
# full_id_unique_keys.
|
|
327
|
+
self._full_id_unique_keys[key] = value
|
|
328
|
+
|
|
308
329
|
# Special handling for pathspec/name
|
|
309
330
|
if other.from_name is not None and other.from_pathspec is not None:
|
|
310
331
|
raise InvalidEnvironmentException(
|
|
@@ -135,6 +135,7 @@ class CondaEnvironment(MetaflowEnvironment):
|
|
|
135
135
|
step.name,
|
|
136
136
|
base_env,
|
|
137
137
|
base_from_full_id=base_from_full_id,
|
|
138
|
+
full_id_unique_keys=req.full_id_unique_keys,
|
|
138
139
|
)
|
|
139
140
|
|
|
140
141
|
resolver.resolve_environments(echo)
|
|
@@ -372,7 +373,11 @@ class CondaEnvironment(MetaflowEnvironment):
|
|
|
372
373
|
elif step_info[2]:
|
|
373
374
|
# In this case, we should know about the environment -- it will have
|
|
374
375
|
# _default flag at this time
|
|
375
|
-
resolved_env = conda.environment(
|
|
376
|
+
resolved_env = conda.environment(
|
|
377
|
+
step_info[2][0],
|
|
378
|
+
local_only=True,
|
|
379
|
+
full_id_unique_keys=step_info[1].full_id_unique_keys,
|
|
380
|
+
)
|
|
376
381
|
if resolved_env:
|
|
377
382
|
return resolved_env.env_id
|
|
378
383
|
return None
|
|
@@ -9,7 +9,11 @@ from metaflow import FlowMutator
|
|
|
9
9
|
from .conda_common_decorator import StepRequirementMixin
|
|
10
10
|
from .conda_flow_decorator import PackageRequirementFlowDecorator
|
|
11
11
|
from .parsers import req_parser, toml_parser
|
|
12
|
-
from .utils import
|
|
12
|
+
from .utils import (
|
|
13
|
+
call_binary,
|
|
14
|
+
determine_uv_env_python_version,
|
|
15
|
+
compute_file_hash,
|
|
16
|
+
)
|
|
13
17
|
|
|
14
18
|
|
|
15
19
|
if TYPE_CHECKING:
|
|
@@ -171,6 +175,9 @@ class ResolvedEnvironmentBaseFlowMutator(FlowMutator):
|
|
|
171
175
|
# See _parse_requirements_txt() for the format of this dict.
|
|
172
176
|
user_deps: Dict[str, str],
|
|
173
177
|
user_sources: List[str],
|
|
178
|
+
# takes a full_id_unique_keys dict rather than uv_content_hash
|
|
179
|
+
# to allow more flexibility in the future.
|
|
180
|
+
full_id_unique_keys: Dict[str, str],
|
|
174
181
|
):
|
|
175
182
|
ResolvedEnvironmentBaseFlowMutator._verify_no_conflict_step_decorators(
|
|
176
183
|
step_name, step
|
|
@@ -181,6 +188,7 @@ class ResolvedEnvironmentBaseFlowMutator(FlowMutator):
|
|
|
181
188
|
"path": pylock_toml_path,
|
|
182
189
|
"user_deps_for_hash": user_deps,
|
|
183
190
|
"user_sources_for_hash": user_sources,
|
|
191
|
+
"full_id_unique_keys": full_id_unique_keys,
|
|
184
192
|
},
|
|
185
193
|
)
|
|
186
194
|
|
|
@@ -290,14 +298,23 @@ class ResolvedUVEnvFlowDecorator(ResolvedEnvironmentBaseFlowMutator):
|
|
|
290
298
|
f"must exist in the specified path {proj_config_dir}"
|
|
291
299
|
)
|
|
292
300
|
|
|
293
|
-
temp_toml_file_path, user_deps, user_sources =
|
|
294
|
-
|
|
295
|
-
|
|
301
|
+
temp_toml_file_path, user_deps, user_sources, uv_lock_file_content_hash = (
|
|
302
|
+
self._handle_uv_lock_scenario(
|
|
303
|
+
os.path.join(proj_config_dir, "uv.lock"),
|
|
304
|
+
os.path.join(proj_config_dir, "pyproject.toml"),
|
|
305
|
+
)
|
|
296
306
|
)
|
|
297
307
|
|
|
298
308
|
for step_name, step in mutable_flow.steps:
|
|
299
309
|
ResolvedEnvironmentBaseFlowMutator._mutate_step(
|
|
300
|
-
step_name,
|
|
310
|
+
step_name,
|
|
311
|
+
step,
|
|
312
|
+
temp_toml_file_path,
|
|
313
|
+
user_deps,
|
|
314
|
+
user_sources,
|
|
315
|
+
full_id_unique_keys={
|
|
316
|
+
"uv_lock_content_hash": uv_lock_file_content_hash,
|
|
317
|
+
},
|
|
301
318
|
)
|
|
302
319
|
|
|
303
320
|
# @staticmethod
|
|
@@ -306,10 +323,11 @@ class ResolvedUVEnvFlowDecorator(ResolvedEnvironmentBaseFlowMutator):
|
|
|
306
323
|
# ) -> tuple[Optional[str], Dict[str, str]]:
|
|
307
324
|
# raise NotImplementedError("This will be supported in a stacked PR soon.")
|
|
308
325
|
|
|
326
|
+
# Returns: (temp_pylock_toml_path, user_deps_dict, user_sources_list, uv_lock_content_hash)
|
|
309
327
|
@staticmethod
|
|
310
328
|
def _handle_uv_lock_scenario(
|
|
311
329
|
uv_lock_path: str, pyproject_toml_path: str
|
|
312
|
-
) -> Tuple[
|
|
330
|
+
) -> Tuple[str, Dict[str, str], List[str], str]:
|
|
313
331
|
# 1. read pyproject.toml
|
|
314
332
|
user_deps, user_srcs = (
|
|
315
333
|
ResolvedUVEnvFlowDecorator._parse_pyproject_toml_to_user_deps_dict(
|
|
@@ -337,7 +355,12 @@ class ResolvedUVEnvFlowDecorator(ResolvedEnvironmentBaseFlowMutator):
|
|
|
337
355
|
cwd=cwd,
|
|
338
356
|
)
|
|
339
357
|
|
|
340
|
-
|
|
358
|
+
# Calculate uv lock file content hash, to be passed to PylockToml Resolver,
|
|
359
|
+
# and eventually ResolvedEnvironment, to be a component of full_id hash calculation,
|
|
360
|
+
# and will be used to invalidate the cache if pyproject.toml is the same but uv.lock changes.
|
|
361
|
+
uv_lock_content_hash = compute_file_hash(uv_lock_path)
|
|
362
|
+
|
|
363
|
+
return temp_path, user_deps, user_srcs, uv_lock_content_hash
|
|
341
364
|
|
|
342
365
|
@staticmethod
|
|
343
366
|
def _parse_pyproject_toml_to_user_deps_dict(
|
|
@@ -392,6 +392,16 @@ class PylockTomlInternalDecorator(StepRequirementMixin, StepDecorator):
|
|
|
392
392
|
"user_deps_for_hash": {},
|
|
393
393
|
# The purpose is similar to user_deps_for_hash.
|
|
394
394
|
"user_sources_for_hash": [],
|
|
395
|
+
# When a pylock_internal decorator is generated, the originating
|
|
396
|
+
# ResolvedUvEnvFlowDecorator calculates the hash of uv.lock, assigns it
|
|
397
|
+
# to the decorator's attributes["full_id_unique_keys"]["uv_content_hash"],
|
|
398
|
+
# and eventually this uv content hash value will be passed to
|
|
399
|
+
# PylockTomlInternalDecorator.resolve()'s parameters.
|
|
400
|
+
#
|
|
401
|
+
# This is used to detect a uv.lock change with unchanged user
|
|
402
|
+
# dependencies in pyproject.toml, caused by design by uv's non-determinism
|
|
403
|
+
# in resolution when ">=version" specifiers are involved.
|
|
404
|
+
"full_id_unique_keys": {},
|
|
395
405
|
}
|
|
396
406
|
|
|
397
407
|
name = "pylock_toml_internal"
|
|
@@ -445,6 +455,10 @@ class PylockTomlInternalDecorator(StepRequirementMixin, StepDecorator):
|
|
|
445
455
|
def python(self) -> Optional[str]:
|
|
446
456
|
return self.attributes["user_deps_for_hash"].get("python")
|
|
447
457
|
|
|
458
|
+
@property
|
|
459
|
+
def full_id_unique_keys(self) -> Dict[str, str]:
|
|
460
|
+
return cast(Dict[str, str], self.attributes["full_id_unique_keys"])
|
|
461
|
+
|
|
448
462
|
|
|
449
463
|
class CondaEnvInternalDecorator(StepDecorator):
|
|
450
464
|
name = "conda_env_internal"
|
|
@@ -793,6 +793,8 @@ class ResolvedEnvironment:
|
|
|
793
793
|
co_resolved: Optional[List[str]] = None,
|
|
794
794
|
env_type: EnvType = EnvType.MIXED,
|
|
795
795
|
accurate_source: bool = True,
|
|
796
|
+
full_id_unique_keys: Optional[Dict[str, str]] = None,
|
|
797
|
+
# Sample format: {'uv_lock_content': '33746974cb80adfea6047b5c5713c59c0dbc721da3179501c1bad261241cd3d3'}
|
|
796
798
|
):
|
|
797
799
|
self._env_type = env_type
|
|
798
800
|
self._user_dependencies = dict_to_tstr(user_dependencies)
|
|
@@ -803,6 +805,7 @@ class ResolvedEnvironment:
|
|
|
803
805
|
self._user_extra_args = dict_to_tstr(user_extra_args) if user_extra_args else []
|
|
804
806
|
|
|
805
807
|
self._accurate_source = accurate_source
|
|
808
|
+
self._full_id_unique_keys = full_id_unique_keys or {}
|
|
806
809
|
|
|
807
810
|
if not env_id:
|
|
808
811
|
env_req_id = ResolvedEnvironment.get_req_id(
|
|
@@ -813,13 +816,10 @@ class ResolvedEnvironment:
|
|
|
813
816
|
|
|
814
817
|
env_full_id = "_unresolved"
|
|
815
818
|
if all_packages is not None:
|
|
816
|
-
env_full_id = self.
|
|
817
|
-
|
|
818
|
-
"%s#%s" % (p.filename, p.pkg_hash(p.url_format))
|
|
819
|
-
for p in sorted(all_packages, key=lambda p: p.filename)
|
|
820
|
-
]
|
|
821
|
-
+ [arch or arch_id()]
|
|
819
|
+
env_full_id = self._compute_full_id(
|
|
820
|
+
all_packages, arch or arch_id(), self._full_id_unique_keys
|
|
822
821
|
)
|
|
822
|
+
|
|
823
823
|
self._env_id = EnvID(
|
|
824
824
|
req_id=env_req_id, full_id=env_full_id, arch=arch or arch_id()
|
|
825
825
|
)
|
|
@@ -937,10 +937,12 @@ class ResolvedEnvironment:
|
|
|
937
937
|
Unique identifier for this environment.
|
|
938
938
|
"""
|
|
939
939
|
if self._env_id.full_id in ("_default", "_unresolved") and self._all_packages:
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
940
|
+
env_full_id = self._compute_full_id(
|
|
941
|
+
self._all_packages,
|
|
942
|
+
self._env_id.arch or arch_id(),
|
|
943
|
+
self._full_id_unique_keys,
|
|
943
944
|
)
|
|
945
|
+
|
|
944
946
|
self._env_id = self._env_id._replace(full_id=env_full_id)
|
|
945
947
|
return self._env_id
|
|
946
948
|
|
|
@@ -1225,6 +1227,7 @@ class ResolvedEnvironment:
|
|
|
1225
1227
|
"resolved_archs": self._co_resolved,
|
|
1226
1228
|
"env_type": self._env_type.value,
|
|
1227
1229
|
"accurate_source": self._accurate_source,
|
|
1230
|
+
"full_id_unique_keys": self._full_id_unique_keys,
|
|
1228
1231
|
}
|
|
1229
1232
|
|
|
1230
1233
|
@classmethod
|
|
@@ -1267,12 +1270,54 @@ class ResolvedEnvironment:
|
|
|
1267
1270
|
co_resolved=d["resolved_archs"],
|
|
1268
1271
|
env_type=env_type,
|
|
1269
1272
|
accurate_source=d.get("accurate_source", True),
|
|
1273
|
+
full_id_unique_keys=d.get("full_id_unique_keys", None),
|
|
1270
1274
|
)
|
|
1271
1275
|
|
|
1272
1276
|
@staticmethod
|
|
1273
|
-
def _compute_hash(inputs: Iterable[str]):
|
|
1277
|
+
def _compute_hash(inputs: Iterable[str]) -> str:
|
|
1274
1278
|
return sha1(b" ".join([s.encode("ascii") for s in inputs])).hexdigest()
|
|
1275
1279
|
|
|
1280
|
+
@staticmethod
|
|
1281
|
+
def _unique_keys_to_list(
|
|
1282
|
+
unique_keys: Dict[str, str],
|
|
1283
|
+
) -> List[str]:
|
|
1284
|
+
# Using "{k}:{v}" not "{k}#{v}" to avoid mixing with package filename#hash,
|
|
1285
|
+
# in case this helps debuggability.
|
|
1286
|
+
return [f"{k}:{v}" for k, v in sorted(unique_keys.items())]
|
|
1287
|
+
|
|
1288
|
+
# There are two levels of hashing for the environment:
|
|
1289
|
+
# * the req_id which is based on the user-requested dependencies and sources.
|
|
1290
|
+
# * the full_id which is based on the resolved user dependencies.
|
|
1291
|
+
#
|
|
1292
|
+
# Originally, full_id is composed of only the resolved dependencies and the
|
|
1293
|
+
# architecture, and a req_id can be mapped to a recently resolved full_id.
|
|
1294
|
+
# This justifies the caching mechanism such as the "_default" key under the
|
|
1295
|
+
# req_id entry in the environment manifest.
|
|
1296
|
+
#
|
|
1297
|
+
# However, as we introduced uv,
|
|
1298
|
+
# we found operations such as `uv lock --upgrade` can introduce different
|
|
1299
|
+
# resolved dependencies while keeping the same user-requested dependencies.
|
|
1300
|
+
# As a result, full_id needs to take into consideration full_id_unique_keys
|
|
1301
|
+
# as well.
|
|
1302
|
+
@staticmethod
|
|
1303
|
+
def _compute_full_id(
|
|
1304
|
+
all_packages: Sequence[PackageSpecification],
|
|
1305
|
+
arch: str,
|
|
1306
|
+
full_id_unique_keys: Optional[Dict[str, str]] = None,
|
|
1307
|
+
) -> str:
|
|
1308
|
+
to_hash_list = [
|
|
1309
|
+
"%s#%s" % (p.filename, p.pkg_hash(p.url_format))
|
|
1310
|
+
for p in sorted(all_packages, key=lambda p: p.filename)
|
|
1311
|
+
] + [arch]
|
|
1312
|
+
to_hash_list.extend(
|
|
1313
|
+
ResolvedEnvironment._unique_keys_to_list(full_id_unique_keys)
|
|
1314
|
+
if full_id_unique_keys
|
|
1315
|
+
else []
|
|
1316
|
+
)
|
|
1317
|
+
|
|
1318
|
+
env_full_id = ResolvedEnvironment._compute_hash(to_hash_list)
|
|
1319
|
+
return env_full_id
|
|
1320
|
+
|
|
1276
1321
|
|
|
1277
1322
|
class MetaflowResolvedEnvironment:
|
|
1278
1323
|
"""
|
|
@@ -1511,7 +1556,11 @@ class CachedEnvironmentInfo:
|
|
|
1511
1556
|
# Missing AliasType.REQ_FULL_ID but we don't record aliases for that.
|
|
1512
1557
|
|
|
1513
1558
|
def env_for(
|
|
1514
|
-
self,
|
|
1559
|
+
self,
|
|
1560
|
+
req_id: str,
|
|
1561
|
+
full_id: str = "_default",
|
|
1562
|
+
arch: Optional[str] = None,
|
|
1563
|
+
full_id_unique_keys: Optional[Dict[str, str]] = None,
|
|
1515
1564
|
) -> Optional[ResolvedEnvironment]:
|
|
1516
1565
|
arch = arch or arch_id()
|
|
1517
1566
|
per_arch_envs = self._resolved_environments.get(arch)
|
|
@@ -1520,7 +1569,16 @@ class CachedEnvironmentInfo:
|
|
|
1520
1569
|
if per_req_id_envs:
|
|
1521
1570
|
if full_id == "_default":
|
|
1522
1571
|
full_id = per_req_id_envs.get("_default", "_invalid") # type: ignore
|
|
1523
|
-
|
|
1572
|
+
env = per_req_id_envs.get(full_id) # type: ignore
|
|
1573
|
+
|
|
1574
|
+
if (
|
|
1575
|
+
env
|
|
1576
|
+
and isinstance(env, ResolvedEnvironment)
|
|
1577
|
+
and env._full_id_unique_keys != full_id_unique_keys
|
|
1578
|
+
):
|
|
1579
|
+
env = None
|
|
1580
|
+
return env # type: ignore
|
|
1581
|
+
|
|
1524
1582
|
return None
|
|
1525
1583
|
|
|
1526
1584
|
def envs_for(
|
|
@@ -97,6 +97,7 @@ class EnvsResolver(object):
|
|
|
97
97
|
force: bool = False,
|
|
98
98
|
local_only: bool = False,
|
|
99
99
|
use_latest: str = CONDA_USE_REMOTE_LATEST,
|
|
100
|
+
full_id_unique_keys: Optional[Dict[str, str]] = None,
|
|
100
101
|
) -> Tuple[
|
|
101
102
|
EnvType,
|
|
102
103
|
EnvID,
|
|
@@ -184,11 +185,14 @@ class EnvsResolver(object):
|
|
|
184
185
|
co_resolved=base_env.co_resolved_archs,
|
|
185
186
|
env_type=base_env.env_type,
|
|
186
187
|
accurate_source=base_env.is_info_accurate,
|
|
188
|
+
full_id_unique_keys=full_id_unique_keys,
|
|
187
189
|
)
|
|
188
190
|
resolved_env.dirty = True
|
|
189
191
|
else:
|
|
190
192
|
resolved_env = (
|
|
191
|
-
conda.environment(env_id, local_only, use_latest)
|
|
193
|
+
conda.environment(env_id, local_only, use_latest, full_id_unique_keys)
|
|
194
|
+
if not force
|
|
195
|
+
else None
|
|
192
196
|
)
|
|
193
197
|
|
|
194
198
|
return (
|
|
@@ -216,6 +220,7 @@ class EnvsResolver(object):
|
|
|
216
220
|
use_latest: str = CONDA_USE_REMOTE_LATEST,
|
|
217
221
|
force: bool = False,
|
|
218
222
|
force_co_resolve: bool = False,
|
|
223
|
+
full_id_unique_keys: Optional[Dict[str, str]] = None,
|
|
219
224
|
):
|
|
220
225
|
"""
|
|
221
226
|
Add an environment to resolve to this EnvsResolver. The EnvsResolver will resolve
|
|
@@ -306,6 +311,7 @@ class EnvsResolver(object):
|
|
|
306
311
|
force=force,
|
|
307
312
|
local_only=local_only,
|
|
308
313
|
use_latest=use_latest,
|
|
314
|
+
full_id_unique_keys=full_id_unique_keys,
|
|
309
315
|
)
|
|
310
316
|
|
|
311
317
|
# Check if we have already requested this environment
|
|
@@ -331,6 +337,7 @@ class EnvsResolver(object):
|
|
|
331
337
|
"sources": user_sources,
|
|
332
338
|
"extras": extras,
|
|
333
339
|
"file_paths": file_paths,
|
|
340
|
+
"full_id_unique_keys": full_id_unique_keys,
|
|
334
341
|
"conda_format": (
|
|
335
342
|
[CONDA_PREFERRED_FORMAT]
|
|
336
343
|
if CONDA_PREFERRED_FORMAT and CONDA_PREFERRED_FORMAT != "none"
|
|
@@ -764,6 +771,7 @@ class EnvsResolver(object):
|
|
|
764
771
|
"resolved": builder_env,
|
|
765
772
|
"need_caching": builder_env is None,
|
|
766
773
|
"env_type": EnvType.CONDA_ONLY,
|
|
774
|
+
"full_id_unique_keys": {},
|
|
767
775
|
}
|
|
768
776
|
self._builder_envs[builder_env_id] = builder_env_info
|
|
769
777
|
builders_by_req_id.setdefault(env_id.req_id, []).append(builder_env_id)
|
|
@@ -926,6 +934,7 @@ class EnvsResolver(object):
|
|
|
926
934
|
builder_envs,
|
|
927
935
|
env_desc["base"],
|
|
928
936
|
env_desc["file_paths"],
|
|
937
|
+
env_desc["full_id_unique_keys"],
|
|
929
938
|
)
|
|
930
939
|
|
|
931
940
|
if env_desc["base"]:
|
|
@@ -47,6 +47,8 @@ class CondaResolver(Resolver):
|
|
|
47
47
|
builder_envs: Optional[List[ResolvedEnvironment]] = None,
|
|
48
48
|
base_env: Optional[ResolvedEnvironment] = None,
|
|
49
49
|
file_paths: Dict[str, List[str]] = {},
|
|
50
|
+
# full_id_unique_keys is not used in CondaResolver.
|
|
51
|
+
full_id_unique_keys: Optional[Dict[str, str]] = None,
|
|
50
52
|
) -> Tuple[ResolvedEnvironment, Optional[List[ResolvedEnvironment]]]:
|
|
51
53
|
if base_env:
|
|
52
54
|
local_packages = [
|
|
@@ -174,6 +176,10 @@ class CondaResolver(Resolver):
|
|
|
174
176
|
architecture,
|
|
175
177
|
all_packages=packages,
|
|
176
178
|
env_type=env_type,
|
|
179
|
+
# full_id_unique_keys is a uv specific cache invalidation mechanism,
|
|
180
|
+
# and PipResolver shouldn't need it. We still pass this along as {}
|
|
181
|
+
# to make code consistent.
|
|
182
|
+
full_id_unique_keys=full_id_unique_keys,
|
|
177
183
|
),
|
|
178
184
|
builder_envs,
|
|
179
185
|
)
|
|
@@ -62,6 +62,7 @@ class PipResolver(Resolver):
|
|
|
62
62
|
builder_envs: Optional[List[ResolvedEnvironment]] = None,
|
|
63
63
|
base_env: Optional[ResolvedEnvironment] = None,
|
|
64
64
|
file_paths: Dict[str, List[str]] = {},
|
|
65
|
+
full_id_unique_keys: Dict[str, str] = {},
|
|
65
66
|
) -> Tuple[ResolvedEnvironment, Optional[List[ResolvedEnvironment]]]:
|
|
66
67
|
if base_env:
|
|
67
68
|
# For base environments, we may have built packages already so for those
|
|
@@ -594,6 +595,10 @@ class PipResolver(Resolver):
|
|
|
594
595
|
architecture,
|
|
595
596
|
all_packages=packages,
|
|
596
597
|
env_type=env_type,
|
|
598
|
+
# full_id_unique_keys is a uv specific cache invalidation mechanism,
|
|
599
|
+
# and PipResolver shouldn't need it. We still pass this along as {}
|
|
600
|
+
# to make code consistent.
|
|
601
|
+
full_id_unique_keys=full_id_unique_keys,
|
|
597
602
|
),
|
|
598
603
|
builder_envs,
|
|
599
604
|
)
|
|
@@ -18,7 +18,7 @@ from metaflow._vendor.packaging.tags import (
|
|
|
18
18
|
Tag,
|
|
19
19
|
)
|
|
20
20
|
|
|
21
|
-
from
|
|
21
|
+
from ..utils import CondaException
|
|
22
22
|
|
|
23
23
|
from . import Resolver
|
|
24
24
|
from typing import Callable, Dict, Iterable, List, Any, Optional, Tuple
|
|
@@ -1415,6 +1415,18 @@ def filter_user_reqs_by_markers(
|
|
|
1415
1415
|
return new_deps
|
|
1416
1416
|
|
|
1417
1417
|
|
|
1418
|
+
def compute_file_hash(file_path: str) -> str:
|
|
1419
|
+
if not file_path:
|
|
1420
|
+
return ""
|
|
1421
|
+
|
|
1422
|
+
try:
|
|
1423
|
+
with open(file_path, "rb") as f:
|
|
1424
|
+
file_content_hash = hashlib.sha256(f.read()).hexdigest()
|
|
1425
|
+
return file_content_hash
|
|
1426
|
+
except (IOError, OSError):
|
|
1427
|
+
raise IOError(f"Could not read file '{file_path}' for hashing") from None
|
|
1428
|
+
|
|
1429
|
+
|
|
1418
1430
|
class WithDir:
|
|
1419
1431
|
# WARNING: os.chdir is not compatible with thread processing so do not use in
|
|
1420
1432
|
# a context where multiple threads can exist.
|
|
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
|