13
13
from pathlib import Path
14
14
from shutil import rmtree
15
15
from tempfile import TemporaryDirectory
16
- from typing import Callable , Iterable , Literal , Mapping , Sequence , get_args
16
+ from types import TracebackType
17
+ from typing import (
18
+ Any ,
19
+ Callable ,
20
+ Iterable ,
21
+ Literal ,
22
+ Mapping ,
23
+ Sequence ,
24
+ get_args ,
25
+ overload ,
26
+ )
17
27
from unicodedata import normalize
18
28
19
29
from jinja2 .loaders import FileSystemLoader
36
46
from .subproject import Subproject
37
47
from .template import Task , Template
38
48
from .tools import OS , Style , normalize_git_path , printf , readlink
39
- from .types import (
40
- MISSING ,
41
- AnyByStrDict ,
42
- JSONSerializable ,
43
- OptStr ,
44
- RelativePath ,
45
- StrOrPath ,
46
- StrSeq ,
47
- )
49
+ from .types import MISSING , AnyByStrDict , JSONSerializable , RelativePath , StrOrPath
48
50
from .user_data import DEFAULT_DATA , AnswersMap , Question
49
51
from .vcs import get_git
50
52
@@ -157,16 +159,19 @@ class Worker:
157
159
When `True`, allow usage of unsafe templates.
158
160
159
161
See [unsafe][]
162
+
163
+ skip_answered:
164
+ When `True`, skip questions that have already been answered.
160
165
"""
161
166
162
167
src_path : str | None = None
163
168
dst_path : Path = Path ("." )
164
169
answers_file : RelativePath | None = None
165
- vcs_ref : OptStr = None
170
+ vcs_ref : str | None = None
166
171
data : AnyByStrDict = field (default_factory = dict )
167
- exclude : StrSeq = ()
172
+ exclude : Sequence [ str ] = ()
168
173
use_prereleases : bool = False
169
- skip_if_exists : StrSeq = ()
174
+ skip_if_exists : Sequence [ str ] = ()
170
175
cleanup_on_error : bool = True
171
176
defaults : bool = False
172
177
user_defaults : AnyByStrDict = field (default_factory = dict )
@@ -179,13 +184,26 @@ class Worker:
179
184
skip_answered : bool = False
180
185
181
186
answers : AnswersMap = field (default_factory = AnswersMap , init = False )
182
- _cleanup_hooks : list [Callable ] = field (default_factory = list , init = False )
187
+ _cleanup_hooks : list [Callable [[], None ] ] = field (default_factory = list , init = False )
183
188
184
- def __enter__ (self ):
189
+ def __enter__ (self ) -> Worker :
185
190
"""Allow using worker as a context manager."""
186
191
return self
187
192
188
- def __exit__ (self , type , value , traceback ):
193
+ @overload
194
+ def __exit__ (self , type : None , value : None , traceback : None ) -> None : ...
195
+
196
+ @overload
197
+ def __exit__ (
198
+ self , type : type [BaseException ], value : BaseException , traceback : TracebackType
199
+ ) -> None : ...
200
+
201
+ def __exit__ (
202
+ self ,
203
+ type : type [BaseException ] | None ,
204
+ value : BaseException | None ,
205
+ traceback : TracebackType | None ,
206
+ ) -> None :
189
207
"""Clean up garbage files after worker usage ends."""
190
208
if value is not None :
191
209
# exception was raised from code inside context manager:
@@ -196,7 +214,7 @@ def __exit__(self, type, value, traceback):
196
214
# otherwise clean up and let any exception bubble up
197
215
self ._cleanup ()
198
216
199
- def _cleanup (self ):
217
+ def _cleanup (self ) -> None :
200
218
"""Execute all stored cleanup methods."""
201
219
for method in self ._cleanup_hooks :
202
220
method ()
@@ -226,7 +244,7 @@ def _print_message(self, message: str) -> None:
226
244
if message and not self .quiet :
227
245
print (self ._render_string (message ), file = sys .stderr )
228
246
229
- def _answers_to_remember (self ) -> Mapping :
247
+ def _answers_to_remember (self ) -> Mapping [ str , Any ] :
230
248
"""Get only answers that will be remembered in the copier answers file."""
231
249
# All internal values must appear first
232
250
answers : AnyByStrDict = {}
@@ -273,7 +291,7 @@ def _execute_tasks(self, tasks: Sequence[Task]) -> None:
273
291
with local .cwd (self .subproject .local_abspath ), local .env (** task .extra_env ):
274
292
subprocess .run (task_cmd , shell = use_shell , check = True , env = local .env )
275
293
276
- def _render_context (self ) -> Mapping :
294
+ def _render_context (self ) -> Mapping [ str , Any ] :
277
295
"""Produce render context for Jinja."""
278
296
# Backwards compatibility
279
297
# FIXME Remove it?
@@ -305,7 +323,7 @@ def _path_matcher(self, patterns: Iterable[str]) -> Callable[[Path], bool]:
305
323
spec = PathSpec .from_lines ("gitwildmatch" , normalized_patterns )
306
324
return spec .match_file
307
325
308
- def _solve_render_conflict (self , dst_relpath : Path ):
326
+ def _solve_render_conflict (self , dst_relpath : Path ) -> bool :
309
327
"""Properly solve render conflicts.
310
328
311
329
It can ask the user if running in interactive mode.
@@ -468,7 +486,7 @@ def answers_relpath(self) -> Path:
468
486
return Path (template .render (** self .answers .combined ))
469
487
470
488
@cached_property
471
- def all_exclusions (self ) -> StrSeq :
489
+ def all_exclusions (self ) -> Sequence [ str ] :
472
490
"""Combine default, template and user-chosen exclusions."""
473
491
return self .template .exclude + tuple (self .exclude )
474
492
@@ -766,7 +784,7 @@ def run_recopy(self) -> None:
766
784
f"from `{ self .subproject .answers_relpath } `."
767
785
)
768
786
with replace (self , src_path = self .subproject .template .url ) as new_worker :
769
- return new_worker .run_copy ()
787
+ new_worker .run_copy ()
770
788
771
789
def run_update (self ) -> None :
772
790
"""Update a subproject that was already generated.
@@ -818,7 +836,7 @@ def run_update(self) -> None:
818
836
self ._apply_update ()
819
837
self ._print_message (self .template .message_after_update )
820
838
821
- def _apply_update (self ):
839
+ def _apply_update (self ) -> None : # noqa: C901
822
840
git = get_git ()
823
841
subproject_top = Path (
824
842
git (
@@ -840,8 +858,8 @@ def _apply_update(self):
840
858
data = self .subproject .last_answers ,
841
859
defaults = True ,
842
860
quiet = True ,
843
- src_path = self .subproject .template .url ,
844
- vcs_ref = self .subproject .template .commit ,
861
+ src_path = self .subproject .template .url , # type: ignore[union-attr]
862
+ vcs_ref = self .subproject .template .commit , # type: ignore[union-attr]
845
863
) as old_worker :
846
864
old_worker .run_copy ()
847
865
# Extract diff between temporary destination and real destination
@@ -863,7 +881,7 @@ def _apply_update(self):
863
881
diff = diff_cmd ("--inter-hunk-context=0" )
864
882
# Run pre-migration tasks
865
883
self ._execute_tasks (
866
- self .template .migration_tasks ("before" , self .subproject .template )
884
+ self .template .migration_tasks ("before" , self .subproject .template ) # type: ignore[arg-type]
867
885
)
868
886
# Clear last answers cache to load possible answers migration, if skip_answered flag is not set
869
887
if self .skip_answered is False :
@@ -885,10 +903,10 @@ def _apply_update(self):
885
903
with replace (
886
904
self ,
887
905
dst_path = new_copy / subproject_subdir ,
888
- data = self .answers .combined ,
906
+ data = self .answers .combined , # type: ignore[arg-type]
889
907
defaults = True ,
890
908
quiet = True ,
891
- src_path = self .subproject .template .url ,
909
+ src_path = self .subproject .template .url , # type: ignore[union-attr]
892
910
) as new_worker :
893
911
new_worker .run_copy ()
894
912
compared = dircmp (old_copy , new_copy )
@@ -968,10 +986,10 @@ def _apply_update(self):
968
986
969
987
# Run post-migration tasks
970
988
self ._execute_tasks (
971
- self .template .migration_tasks ("after" , self .subproject .template )
989
+ self .template .migration_tasks ("after" , self .subproject .template ) # type: ignore[arg-type]
972
990
)
973
991
974
- def _git_initialize_repo (self ):
992
+ def _git_initialize_repo (self ) -> None :
975
993
"""Initialize a git repository in the current directory."""
976
994
git = get_git ()
977
995
git ("init" , retcode = None )
@@ -1004,7 +1022,7 @@ def run_copy(
1004
1022
src_path : str ,
1005
1023
dst_path : StrOrPath = "." ,
1006
1024
data : AnyByStrDict | None = None ,
1007
- ** kwargs ,
1025
+ ** kwargs : Any ,
1008
1026
) -> Worker :
1009
1027
"""Copy a template to a destination, from zero.
1010
1028
@@ -1020,7 +1038,7 @@ def run_copy(
1020
1038
1021
1039
1022
1040
def run_recopy (
1023
- dst_path : StrOrPath = "." , data : AnyByStrDict | None = None , ** kwargs
1041
+ dst_path : StrOrPath = "." , data : AnyByStrDict | None = None , ** kwargs : Any
1024
1042
) -> Worker :
1025
1043
"""Update a subproject from its template, discarding subproject evolution.
1026
1044
@@ -1038,7 +1056,7 @@ def run_recopy(
1038
1056
def run_update (
1039
1057
dst_path : StrOrPath = "." ,
1040
1058
data : AnyByStrDict | None = None ,
1041
- ** kwargs ,
1059
+ ** kwargs : Any ,
1042
1060
) -> Worker :
1043
1061
"""Update a subproject, from its template.
1044
1062
@@ -1053,7 +1071,7 @@ def run_update(
1053
1071
return worker
1054
1072
1055
1073
1056
- def _remove_old_files (prefix : Path , cmp : dircmp , rm_common : bool = False ) -> None :
1074
+ def _remove_old_files (prefix : Path , cmp : dircmp [ str ] , rm_common : bool = False ) -> None :
1057
1075
"""Remove files and directories only found in "old" template.
1058
1076
1059
1077
This is an internal helper method used to process a comparison of 2
0 commit comments