1
1
import logging
2
2
import os
3
+ from collections .abc import Mapping , Sequence
3
4
from copy import deepcopy
4
5
from itertools import starmap
5
6
from typing import TYPE_CHECKING
11
12
from dvc .path_info import PathInfo
12
13
from dvc .utils .serialize import dumps_yaml
13
14
14
- from .context import Context , CtxDict , CtxList
15
+ from .context import Context
15
16
from .interpolate import resolve
16
17
17
18
if TYPE_CHECKING :
29
30
DEFAULT_PARAMS_FILE = ParamsDependency .DEFAULT_PARAMS_FILE
30
31
PARAMS_KWD = "params"
31
32
33
+ DEFAULT_SENTINEL = object ()
34
+
32
35
33
36
class DataResolver :
34
- def __init__ (self , repo : "Repo" , yaml_wdir : PathInfo , d ):
37
+ def __init__ (self , repo : "Repo" , yaml_wdir : PathInfo , d : dict ):
35
38
to_import : PathInfo = yaml_wdir / d .get (USE_KWD , DEFAULT_PARAMS_FILE )
36
39
vars_ = d .get (VARS_KWD , {})
37
40
if os .path .exists (to_import ):
38
- self .global_ctx = Context .load_from (
39
- repo .tree , str (to_import ), vars_
40
- )
41
41
self .global_ctx_source = to_import
42
+ self .global_ctx = Context .load_from (repo .tree , str (to_import ))
42
43
else :
44
+ self .global_ctx = Context ()
43
45
self .global_ctx_source = None
44
- self .global_ctx = Context .create (vars_ )
46
+ logger .debug (
47
+ "%s does not exist, it won't be used in parametrization" ,
48
+ to_import ,
49
+ )
45
50
46
- self .data = d
51
+ self .global_ctx .merge_update (vars_ )
52
+ self .data : dict = d
47
53
self ._yaml_wdir = yaml_wdir
48
54
self .repo = repo
49
55
@@ -63,23 +69,34 @@ def _resolve_entry(self, name: str, definition):
63
69
def resolve (self ):
64
70
stages = self .data .get (STAGES_KWD , {})
65
71
data = join (starmap (self ._resolve_entry , stages .items ()))
66
- logger .trace ("Resolved dvc.yaml:\n %s" , dumps_yaml (data ))
67
- return {** self .data , STAGES_KWD : data }
72
+ logger .trace ( # pytype: disable=attribute-error
73
+ "Resolved dvc.yaml:\n %s" , dumps_yaml (data )
74
+ )
75
+ return {STAGES_KWD : data }
68
76
69
- def _resolve_stage (self , context : Context , name , definition ):
77
+ def _resolve_stage (self , context : Context , name : str , definition ) -> dict :
70
78
definition = deepcopy (definition )
71
79
self ._set_context_from (context , definition .pop (SET_KWD , {}))
80
+
72
81
wdir = self ._resolve_wdir (context , definition .get (WDIR_KWD ))
73
- params_file = definition .get (PARAMS_KWD , [])
74
- contexts = []
82
+ if self ._yaml_wdir != wdir :
83
+ logger .debug (
84
+ "Stage %s has different wdir than dvc.yaml file" , name
85
+ )
75
86
87
+ contexts = []
76
88
params_yaml_file = wdir / DEFAULT_PARAMS_FILE
77
- if (self .global_ctx_source != params_yaml_file ) and os .path .exists (
78
- params_yaml_file
79
- ):
80
- contexts .append (
81
- Context .load_from (self .repo .tree , str (params_yaml_file ))
82
- )
89
+ if self .global_ctx_source != params_yaml_file :
90
+ if os .path .exists (params_yaml_file ):
91
+ contexts .append (
92
+ Context .load_from (self .repo .tree , str (params_yaml_file ))
93
+ )
94
+ else :
95
+ logger .debug (
96
+ "%s does not exist for stage %s" , params_yaml_file , name
97
+ )
98
+
99
+ params_file = definition .get (PARAMS_KWD , [])
83
100
for item in params_file :
84
101
if item and isinstance (item , dict ):
85
102
contexts .append (
@@ -88,38 +105,37 @@ def _resolve_stage(self, context: Context, name, definition):
88
105
89
106
context .merge_update (* contexts )
90
107
91
- stage_d = resolve (definition , context )
108
+ logger .trace ( # pytype: disable=attribute-error
109
+ "Context during resolution of stage %s:\n %s" , name , context
110
+ )
111
+ with context .track ():
112
+ stage_d = resolve (definition , context )
113
+
92
114
params = stage_d .get (PARAMS_KWD , []) + context .tracked
93
115
94
116
if params :
95
117
stage_d [PARAMS_KWD ] = params
96
118
return {name : stage_d }
97
119
98
120
def _foreach (self , context : Context , name , foreach_data , in_data ):
99
- assert isinstance (foreach_data , str )
100
- iterables = resolve (foreach_data , context )
101
-
102
- def each_iter (value ):
121
+ def each_iter (value , key = DEFAULT_SENTINEL ):
103
122
c = Context .clone (context )
104
- if isinstance (value , tuple ):
105
- key , val = value
106
- else :
107
- key , val = None , value
108
- c ["item" ] = val
109
- if key is not None :
123
+ c ["item" ] = value
124
+ if key is not DEFAULT_SENTINEL :
110
125
c ["key" ] = key
111
- suff = key or value
112
- return self ._resolve_stage (c , f"{ name } -{ suff } " , in_data )
113
-
114
- if isinstance (iterables , (CtxList , list , tuple )):
115
- gen = map (each_iter , iterables )
116
- elif isinstance (iterables , (CtxDict , dict )):
117
- gen = map (each_iter , iterables .items ())
126
+ suffix = str (key if key is not DEFAULT_SENTINEL else value )
127
+ return self ._resolve_stage (c , f"{ name } -{ suffix } " , in_data )
128
+
129
+ iterable = resolve (foreach_data , context )
130
+ if isinstance (iterable , Sequence ):
131
+ gen = (each_iter (v ) for v in iterable )
132
+ elif isinstance (iterable , Mapping ):
133
+ gen = (each_iter (v , k ) for k , v in iterable .items ())
118
134
else :
119
- raise Exception (f"got type of { type (iterables )} " )
135
+ raise Exception (f"got type of { type (iterable )} " )
120
136
return join (gen )
121
137
122
- def _resolve_wdir (self , context , wdir ) :
138
+ def _resolve_wdir (self , context : Context , wdir : str = None ) -> PathInfo :
123
139
if not wdir :
124
140
return self ._yaml_wdir
125
141
wdir = resolve (wdir , context )
0 commit comments