@@ -26,21 +26,38 @@ class ConfigInvalidError(exceptions.ScopesError):
2626 pass
2727
2828
29- class ScopeConfig (pydantic .BaseModel ):
30- include : tuple [str , ...] = pydantic .Field (default_factory = tuple )
31- exclude : tuple [str , ...] = pydantic .Field (default_factory = tuple )
32-
33-
3429ScopeName = typing .Annotated [
3530 str ,
3631 pydantic .StringConstraints (pattern = SCOPE_NAME_RE , min_length = 1 ),
3732]
3833
3934
40- class Config (pydantic .BaseModel ):
41- scopes : dict [ScopeName , ScopeConfig ]
35+ class FileFilters (pydantic .BaseModel ):
36+ include : tuple [str , ...] = pydantic .Field (default_factory = lambda : ("**/*" ,))
37+ exclude : tuple [str , ...] = pydantic .Field (default_factory = tuple )
38+
39+
40+ class SourceFiles (pydantic .BaseModel ):
41+ files : dict [ScopeName , FileFilters ]
42+
43+
44+ class SourceOther (pydantic .BaseModel ):
45+ other : None
46+
47+
48+ class ScopesConfig (pydantic .BaseModel ):
49+ model_config = pydantic .ConfigDict (extra = "forbid" )
50+
51+ mode : typing .Literal ["serial" , "parallel" ] = "serial"
52+ source : SourceFiles | SourceOther | None = None
4253 merge_queue_scope : str | None = "merge-queue"
4354
55+
56+ class Config (pydantic .BaseModel ):
57+ model_config = pydantic .ConfigDict (extra = "ignore" )
58+
59+ scopes : ScopesConfig
60+
4461 @classmethod
4562 def from_dict (cls , data : dict [str , typing .Any ] | typing .Any ) -> Config : # noqa: ANN401
4663 try :
@@ -60,15 +77,15 @@ def from_yaml(cls, path: str) -> Config:
6077
6178
6279def match_scopes (
63- config : Config ,
6480 files : abc .Iterable [str ],
81+ filters : dict [ScopeName , FileFilters ],
6582) -> tuple [set [str ], dict [str , list [str ]]]:
6683 scopes_hit : set [str ] = set ()
67- per_scope : dict [str , list [str ]] = {s : [] for s in config . scopes }
84+ per_scope : dict [str , list [str ]] = {s : [] for s in filters }
6885 for f in files :
6986 # NOTE(sileht): we use pathlib.full_match to support **, as fnmatch does not
7087 p = pathlib .PurePosixPath (f )
71- for scope , scope_config in config . scopes .items ():
88+ for scope , scope_config in filters .items ():
7289 if not scope_config .include and not scope_config .exclude :
7390 continue
7491
@@ -128,14 +145,32 @@ def load_from_file(cls, filename: str) -> DetectedScope:
128145def detect (config_path : str ) -> DetectedScope :
129146 cfg = Config .from_yaml (config_path )
130147 base = base_detector .detect ()
131- changed = changed_files .git_changed_files (base .ref )
132- scopes_hit , per_scope = match_scopes (cfg , changed )
133148
134- all_scopes = set (cfg .scopes .keys ())
135- if cfg .merge_queue_scope is not None :
136- all_scopes .add (cfg .merge_queue_scope )
149+ scopes_hit : set [str ]
150+ per_scope : dict [str , list [str ]]
151+
152+ source = cfg .scopes .source
153+ if source is None :
154+ all_scopes = set ()
155+ scopes_hit = set ()
156+ per_scope = {}
157+ elif isinstance (source , SourceFiles ):
158+ changed = changed_files .git_changed_files (base .ref )
159+ all_scopes = set (source .files .keys ())
160+ scopes_hit , per_scope = match_scopes (changed , source .files )
161+ elif isinstance (source , SourceOther ):
162+ msg = (
163+ "source `other` has been set, scopes must be send with `scopes-send` or API"
164+ )
165+ raise exceptions .ScopesError (msg )
166+ else :
167+ msg = "Unsupported source type" # type:ignore[unreachable]
168+ raise RuntimeError (msg )
169+
170+ if cfg .scopes .merge_queue_scope is not None :
171+ all_scopes .add (cfg .scopes .merge_queue_scope )
137172 if base .is_merge_queue :
138- scopes_hit .add (cfg .merge_queue_scope )
173+ scopes_hit .add (cfg .scopes . merge_queue_scope )
139174
140175 click .echo (f"Base: { base .ref } " )
141176 if scopes_hit :
0 commit comments