ansible-core 2.19.0b5__py3-none-any.whl → 2.19.0b6__py3-none-any.whl

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.
Files changed (117) hide show
  1. ansible/_internal/_ansiballz/__init__.py +0 -0
  2. ansible/_internal/_ansiballz/_builder.py +101 -0
  3. ansible/_internal/{_ansiballz.py → _ansiballz/_wrapper.py} +11 -11
  4. ansible/_internal/_templating/_jinja_bits.py +7 -4
  5. ansible/_internal/_templating/_jinja_plugins.py +5 -2
  6. ansible/_internal/_templating/_template_vars.py +72 -0
  7. ansible/_internal/_templating/_transform.py +6 -0
  8. ansible/_internal/_yaml/_constructor.py +4 -4
  9. ansible/_internal/_yaml/_dumper.py +26 -18
  10. ansible/cli/__init__.py +7 -12
  11. ansible/cli/arguments/option_helpers.py +1 -1
  12. ansible/cli/console.py +1 -1
  13. ansible/cli/doc.py +2 -2
  14. ansible/cli/inventory.py +5 -7
  15. ansible/config/base.yml +24 -0
  16. ansible/errors/__init__.py +2 -1
  17. ansible/executor/module_common.py +67 -39
  18. ansible/executor/process/worker.py +2 -2
  19. ansible/galaxy/api.py +1 -4
  20. ansible/galaxy/collection/__init__.py +1 -6
  21. ansible/galaxy/collection/concrete_artifact_manager.py +2 -8
  22. ansible/galaxy/role.py +2 -2
  23. ansible/module_utils/_internal/__init__.py +7 -4
  24. ansible/module_utils/_internal/_ansiballz/__init__.py +0 -0
  25. ansible/module_utils/_internal/_ansiballz/_extensions/__init__.py +0 -0
  26. ansible/module_utils/_internal/_ansiballz/_extensions/_coverage.py +45 -0
  27. ansible/module_utils/_internal/_ansiballz/_extensions/_pydevd.py +62 -0
  28. ansible/module_utils/_internal/{_ansiballz.py → _ansiballz/_loader.py} +10 -38
  29. ansible/module_utils/_internal/_ansiballz/_respawn.py +32 -0
  30. ansible/module_utils/_internal/_ansiballz/_respawn_wrapper.py +23 -0
  31. ansible/module_utils/_internal/_datatag/__init__.py +23 -1
  32. ansible/module_utils/_internal/_deprecator.py +27 -33
  33. ansible/module_utils/_internal/_json/_profiles/__init__.py +1 -0
  34. ansible/module_utils/_internal/_messages.py +26 -2
  35. ansible/module_utils/_internal/_plugin_info.py +14 -1
  36. ansible/module_utils/ansible_release.py +1 -1
  37. ansible/module_utils/basic.py +46 -56
  38. ansible/module_utils/common/respawn.py +4 -41
  39. ansible/module_utils/connection.py +8 -11
  40. ansible/module_utils/facts/hardware/linux.py +1 -1
  41. ansible/module_utils/facts/sysctl.py +4 -6
  42. ansible/module_utils/facts/system/caps.py +2 -2
  43. ansible/module_utils/facts/system/local.py +1 -1
  44. ansible/module_utils/facts/virtual/linux.py +1 -1
  45. ansible/module_utils/service.py +1 -1
  46. ansible/module_utils/urls.py +4 -4
  47. ansible/modules/apt_repository.py +10 -10
  48. ansible/modules/assemble.py +2 -2
  49. ansible/modules/async_wrapper.py +7 -17
  50. ansible/modules/command.py +3 -3
  51. ansible/modules/copy.py +4 -4
  52. ansible/modules/cron.py +1 -1
  53. ansible/modules/file.py +16 -17
  54. ansible/modules/find.py +3 -3
  55. ansible/modules/get_url.py +17 -0
  56. ansible/modules/git.py +9 -7
  57. ansible/modules/known_hosts.py +12 -14
  58. ansible/modules/package.py +6 -0
  59. ansible/modules/replace.py +2 -2
  60. ansible/modules/slurp.py +10 -13
  61. ansible/modules/stat.py +5 -7
  62. ansible/modules/unarchive.py +6 -6
  63. ansible/modules/user.py +1 -1
  64. ansible/modules/wait_for.py +28 -30
  65. ansible/modules/yum_repository.py +4 -3
  66. ansible/parsing/dataloader.py +2 -2
  67. ansible/parsing/vault/__init__.py +6 -10
  68. ansible/playbook/base.py +7 -2
  69. ansible/playbook/included_file.py +3 -1
  70. ansible/playbook/play_context.py +2 -0
  71. ansible/playbook/taggable.py +19 -5
  72. ansible/playbook/task.py +2 -0
  73. ansible/plugins/action/fetch.py +3 -3
  74. ansible/plugins/action/template.py +8 -2
  75. ansible/plugins/cache/__init__.py +17 -19
  76. ansible/plugins/callback/tree.py +5 -5
  77. ansible/plugins/connection/local.py +4 -4
  78. ansible/plugins/connection/paramiko_ssh.py +5 -5
  79. ansible/plugins/connection/ssh.py +8 -6
  80. ansible/plugins/connection/winrm.py +1 -1
  81. ansible/plugins/filter/core.py +19 -21
  82. ansible/plugins/filter/encryption.py +10 -2
  83. ansible/plugins/list.py +5 -4
  84. ansible/plugins/lookup/template.py +9 -4
  85. ansible/plugins/shell/powershell.py +3 -2
  86. ansible/plugins/shell/sh.py +3 -2
  87. ansible/plugins/strategy/__init__.py +3 -3
  88. ansible/plugins/test/core.py +2 -2
  89. ansible/release.py +1 -1
  90. ansible/template/__init__.py +9 -53
  91. ansible/utils/collection_loader/_collection_finder.py +3 -3
  92. ansible/utils/display.py +23 -12
  93. ansible/utils/galaxy.py +2 -2
  94. ansible/utils/hashing.py +6 -7
  95. ansible/utils/path.py +5 -7
  96. ansible/utils/py3compat.py +2 -1
  97. ansible/utils/ssh_functions.py +3 -2
  98. ansible/vars/plugins.py +3 -3
  99. {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b6.dist-info}/METADATA +1 -1
  100. {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b6.dist-info}/RECORD +117 -108
  101. ansible_test/_internal/commands/integration/coverage.py +7 -2
  102. ansible_test/_internal/host_profiles.py +62 -10
  103. ansible_test/_internal/provisioning.py +10 -4
  104. ansible_test/_internal/ssh.py +1 -5
  105. ansible_test/_internal/thread.py +2 -1
  106. ansible_test/_internal/timeout.py +1 -1
  107. ansible_test/_internal/util.py +20 -12
  108. ansible_test/_util/target/setup/requirements.py +3 -9
  109. {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b6.dist-info}/WHEEL +0 -0
  110. {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b6.dist-info}/entry_points.txt +0 -0
  111. {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b6.dist-info}/licenses/COPYING +0 -0
  112. {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b6.dist-info}/licenses/licenses/Apache-License.txt +0 -0
  113. {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b6.dist-info}/licenses/licenses/BSD-3-Clause.txt +0 -0
  114. {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b6.dist-info}/licenses/licenses/MIT-license.txt +0 -0
  115. {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b6.dist-info}/licenses/licenses/PSF-license.txt +0 -0
  116. {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b6.dist-info}/licenses/licenses/simplified_bsd.txt +0 -0
  117. {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b6.dist-info}/top_level.txt +0 -0
@@ -3,6 +3,7 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import abc
6
+ import json
6
7
  import os
7
8
  import shutil
8
9
  import tempfile
@@ -240,9 +241,13 @@ class PosixCoverageHandler(CoverageHandler[PosixConfig]):
240
241
  # cause the 'coverage' module to be found, but not imported or enabled
241
242
  coverage_file = ''
242
243
 
244
+ coverage_options = dict(
245
+ config=config_file,
246
+ output=coverage_file,
247
+ )
248
+
243
249
  variables = dict(
244
- _ANSIBLE_COVERAGE_CONFIG=config_file,
245
- _ANSIBLE_COVERAGE_OUTPUT=coverage_file,
250
+ _ANSIBLE_ANSIBALLZ_COVERAGE_CONFIG=json.dumps(coverage_options),
246
251
  )
247
252
 
248
253
  return variables
@@ -206,7 +206,7 @@ class Inventory:
206
206
  inventory_text += f'[{group}]\n'
207
207
 
208
208
  for host, variables in hosts.items():
209
- kvp = ' '.join(f'{key}="{value}"' for key, value in variables.items())
209
+ kvp = ' '.join(f"{key}={value!r}" for key, value in variables.items())
210
210
  inventory_text += f'{host} {kvp}\n'
211
211
 
212
212
  inventory_text += '\n'
@@ -235,18 +235,24 @@ class HostProfile(t.Generic[THostConfig], metaclass=abc.ABCMeta):
235
235
  *,
236
236
  args: EnvironmentConfig,
237
237
  config: THostConfig,
238
- targets: t.Optional[list[HostConfig]],
238
+ controller: ControllerHostProfile,
239
239
  ) -> None:
240
240
  self.args = args
241
241
  self.config = config
242
- self.controller = bool(targets)
243
- self.targets = targets or []
242
+ self.controller = not controller # this profile is a controller whenever the `controller` arg was not provided
243
+ self.targets = args.targets if self.controller else [] # only keep targets if this profile is a controller
244
+ self.controller_profile = controller if isinstance(self, ControllerProfile) else None
244
245
 
245
246
  self.state: dict[str, t.Any] = {}
246
247
  """State that must be persisted across delegation."""
247
248
  self.cache: dict[str, t.Any] = {}
248
249
  """Cache that must not be persisted across delegation."""
249
250
 
251
+ @property
252
+ @abc.abstractmethod
253
+ def name(self) -> str:
254
+ """The name of the host profile."""
255
+
250
256
  def provision(self) -> None:
251
257
  """Provision the host before delegation."""
252
258
 
@@ -274,6 +280,9 @@ class HostProfile(t.Generic[THostConfig], metaclass=abc.ABCMeta):
274
280
  # args will be populated after the instances are restored
275
281
  self.cache = {}
276
282
 
283
+ def __str__(self) -> str:
284
+ return f'{self.__class__.__name__}: {self.name}'
285
+
277
286
 
278
287
  class PosixProfile(HostProfile[TPosixConfig], metaclass=abc.ABCMeta):
279
288
  """Base class for POSIX host profiles."""
@@ -320,6 +329,11 @@ class SshTargetHostProfile(HostProfile[THostConfig], metaclass=abc.ABCMeta):
320
329
  class RemoteProfile(SshTargetHostProfile[TRemoteConfig], metaclass=abc.ABCMeta):
321
330
  """Base class for remote instance profiles."""
322
331
 
332
+ @property
333
+ def name(self) -> str:
334
+ """The name of the host profile."""
335
+ return self.config.name
336
+
323
337
  @property
324
338
  def core_ci_state(self) -> t.Optional[dict[str, str]]:
325
339
  """The saved Ansible Core CI state."""
@@ -339,6 +353,8 @@ class RemoteProfile(SshTargetHostProfile[TRemoteConfig], metaclass=abc.ABCMeta):
339
353
 
340
354
  def deprovision(self) -> None:
341
355
  """Deprovision the host after delegation has completed."""
356
+ super().deprovision()
357
+
342
358
  if self.args.remote_terminate == TerminateMode.ALWAYS or (self.args.remote_terminate == TerminateMode.SUCCESS and self.args.success):
343
359
  self.delete_instance()
344
360
 
@@ -397,6 +413,11 @@ class RemoteProfile(SshTargetHostProfile[TRemoteConfig], metaclass=abc.ABCMeta):
397
413
  class ControllerProfile(SshTargetHostProfile[ControllerConfig], PosixProfile[ControllerConfig]):
398
414
  """Host profile for the controller as a target."""
399
415
 
416
+ @property
417
+ def name(self) -> str:
418
+ """The name of the host profile."""
419
+ return self.controller_profile.name
420
+
400
421
  def get_controller_target_connections(self) -> list[SshConnection]:
401
422
  """Return SSH connection(s) for accessing the host as a target from the controller."""
402
423
  settings = SshConnectionDetail(
@@ -425,6 +446,11 @@ class DockerProfile(ControllerHostProfile[DockerConfig], SshTargetHostProfile[Do
425
446
  command_privileged: bool
426
447
  expected_mounts: tuple[CGroupMount, ...]
427
448
 
449
+ @property
450
+ def name(self) -> str:
451
+ """The name of the host profile."""
452
+ return self.config.name
453
+
428
454
  @property
429
455
  def container_name(self) -> t.Optional[str]:
430
456
  """Return the stored container name, if any, otherwise None."""
@@ -976,6 +1002,8 @@ class DockerProfile(ControllerHostProfile[DockerConfig], SshTargetHostProfile[Do
976
1002
 
977
1003
  def deprovision(self) -> None:
978
1004
  """Deprovision the host after delegation has completed."""
1005
+ super().deprovision()
1006
+
979
1007
  container_exists = False
980
1008
 
981
1009
  if self.container_name:
@@ -1025,10 +1053,10 @@ class DockerProfile(ControllerHostProfile[DockerConfig], SshTargetHostProfile[Do
1025
1053
 
1026
1054
  raise HostConnectionError(f'Timeout waiting for {self.config.name} container {self.container_name}.', callback)
1027
1055
 
1028
- def get_controller_target_connections(self) -> list[SshConnection]:
1029
- """Return SSH connection(s) for accessing the host as a target from the controller."""
1056
+ def get_ssh_connection_detail(self, host_type: str) -> SshConnectionDetail:
1057
+ """Return SSH connection detail for the specified host type."""
1030
1058
  containers = get_container_database(self.args)
1031
- access = containers.data[HostType.control]['__test_hosts__'][self.container_name]
1059
+ access = containers.data[host_type]['__test_hosts__'][self.container_name]
1032
1060
 
1033
1061
  host = access.host_ip
1034
1062
  port = dict(access.port_map())[22]
@@ -1046,7 +1074,11 @@ class DockerProfile(ControllerHostProfile[DockerConfig], SshTargetHostProfile[Do
1046
1074
  enable_rsa_sha1='centos6' in self.config.image,
1047
1075
  )
1048
1076
 
1049
- return [SshConnection(self.args, settings)]
1077
+ return settings
1078
+
1079
+ def get_controller_target_connections(self) -> list[SshConnection]:
1080
+ """Return SSH connection(s) for accessing the host as a target from the controller."""
1081
+ return [SshConnection(self.args, self.get_ssh_connection_detail(HostType.control))]
1050
1082
 
1051
1083
  def get_origin_controller_connection(self) -> DockerConnection:
1052
1084
  """Return a connection for accessing the host as a controller from the origin."""
@@ -1116,6 +1148,11 @@ class DockerProfile(ControllerHostProfile[DockerConfig], SshTargetHostProfile[Do
1116
1148
  class NetworkInventoryProfile(HostProfile[NetworkInventoryConfig]):
1117
1149
  """Host profile for a network inventory."""
1118
1150
 
1151
+ @property
1152
+ def name(self) -> str:
1153
+ """The name of the host profile."""
1154
+ return self.config.path
1155
+
1119
1156
 
1120
1157
  class NetworkRemoteProfile(RemoteProfile[NetworkRemoteConfig]):
1121
1158
  """Host profile for a network remote instance."""
@@ -1197,6 +1234,11 @@ class NetworkRemoteProfile(RemoteProfile[NetworkRemoteConfig]):
1197
1234
  class OriginProfile(ControllerHostProfile[OriginConfig]):
1198
1235
  """Host profile for origin."""
1199
1236
 
1237
+ @property
1238
+ def name(self) -> str:
1239
+ """The name of the host profile."""
1240
+ return 'origin'
1241
+
1200
1242
  def get_origin_controller_connection(self) -> LocalConnection:
1201
1243
  """Return a connection for accessing the host as a controller from the origin."""
1202
1244
  return LocalConnection(self.args)
@@ -1317,6 +1359,11 @@ class PosixRemoteProfile(ControllerHostProfile[PosixRemoteConfig], RemoteProfile
1317
1359
  class PosixSshProfile(SshTargetHostProfile[PosixSshConfig], PosixProfile[PosixSshConfig]):
1318
1360
  """Host profile for a POSIX SSH instance."""
1319
1361
 
1362
+ @property
1363
+ def name(self) -> str:
1364
+ """The name of the host profile."""
1365
+ return self.config.host
1366
+
1320
1367
  def get_controller_target_connections(self) -> list[SshConnection]:
1321
1368
  """Return SSH connection(s) for accessing the host as a target from the controller."""
1322
1369
  settings = SshConnectionDetail(
@@ -1334,6 +1381,11 @@ class PosixSshProfile(SshTargetHostProfile[PosixSshConfig], PosixProfile[PosixSs
1334
1381
  class WindowsInventoryProfile(SshTargetHostProfile[WindowsInventoryConfig]):
1335
1382
  """Host profile for a Windows inventory."""
1336
1383
 
1384
+ @property
1385
+ def name(self) -> str:
1386
+ """The name of the host profile."""
1387
+ return self.config.path
1388
+
1337
1389
  def get_controller_target_connections(self) -> list[SshConnection]:
1338
1390
  """Return SSH connection(s) for accessing the host as a target from the controller."""
1339
1391
  inventory = parse_inventory(self.args, self.config.path)
@@ -1436,9 +1488,9 @@ def get_config_profile_type_map() -> dict[t.Type[HostConfig], t.Type[HostProfile
1436
1488
  def create_host_profile(
1437
1489
  args: EnvironmentConfig,
1438
1490
  config: HostConfig,
1439
- controller: bool,
1491
+ controller: ControllerHostProfile | None,
1440
1492
  ) -> HostProfile:
1441
1493
  """Create and return a host profile from the given host configuration."""
1442
1494
  profile_type = get_config_profile_type_map()[type(config)]
1443
- profile = profile_type(args=args, config=config, targets=args.targets if controller else None)
1495
+ profile = profile_type(args=args, config=config, controller=controller)
1444
1496
  return profile
@@ -116,9 +116,11 @@ def prepare_profiles(
116
116
  else:
117
117
  run_pypi_proxy(args, targets_use_pypi)
118
118
 
119
+ controller_host_profile = t.cast(ControllerHostProfile, create_host_profile(args, args.controller, None))
120
+
119
121
  host_state = HostState(
120
- controller_profile=t.cast(ControllerHostProfile, create_host_profile(args, args.controller, True)),
121
- target_profiles=[create_host_profile(args, target, False) for target in args.targets],
122
+ controller_profile=controller_host_profile,
123
+ target_profiles=[create_host_profile(args, target, controller_host_profile) for target in args.targets],
122
124
  )
123
125
 
124
126
  if args.prime_containers:
@@ -137,7 +139,9 @@ def prepare_profiles(
137
139
  if not skip_setup:
138
140
  profile.setup()
139
141
 
140
- dispatch_jobs([(profile, WrappedThread(functools.partial(provision, profile))) for profile in host_state.profiles])
142
+ dispatch_jobs(
143
+ [(profile, WrappedThread(functools.partial(provision, profile), f'Provision: {profile}')) for profile in host_state.profiles]
144
+ )
141
145
 
142
146
  host_state.controller_profile.configure()
143
147
 
@@ -157,7 +161,9 @@ def prepare_profiles(
157
161
  if requirements:
158
162
  requirements(profile)
159
163
 
160
- dispatch_jobs([(profile, WrappedThread(functools.partial(configure, profile))) for profile in host_state.target_profiles])
164
+ dispatch_jobs(
165
+ [(profile, WrappedThread(functools.partial(configure, profile), f'Configure: {profile}')) for profile in host_state.target_profiles]
166
+ )
161
167
 
162
168
  return host_state
163
169
 
@@ -13,7 +13,6 @@ import shlex
13
13
  import typing as t
14
14
 
15
15
  from .encoding import (
16
- to_bytes,
17
16
  to_text,
18
17
  )
19
18
 
@@ -223,13 +222,10 @@ def run_ssh_command(
223
222
  cmd_show = shlex.join(cmd)
224
223
  display.info('Run background command: %s' % cmd_show, verbosity=1, truncate=True)
225
224
 
226
- cmd_bytes = [to_bytes(arg) for arg in cmd]
227
- env_bytes = dict((to_bytes(k), to_bytes(v)) for k, v in env.items())
228
-
229
225
  if args.explain:
230
226
  process = SshProcess(None)
231
227
  else:
232
- process = SshProcess(subprocess.Popen(cmd_bytes, env=env_bytes, bufsize=-1, # pylint: disable=consider-using-with
228
+ process = SshProcess(subprocess.Popen(cmd, env=env, bufsize=-1, # pylint: disable=consider-using-with
233
229
  stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=subprocess.PIPE))
234
230
 
235
231
  return process
@@ -17,11 +17,12 @@ TCallable = t.TypeVar('TCallable', bound=t.Callable[..., t.Any])
17
17
  class WrappedThread(threading.Thread):
18
18
  """Wrapper around Thread which captures results and exceptions."""
19
19
 
20
- def __init__(self, action: c.Callable[[], t.Any]) -> None:
20
+ def __init__(self, action: c.Callable[[], t.Any], name: str) -> None:
21
21
  super().__init__()
22
22
  self._result: queue.Queue[t.Any] = queue.Queue()
23
23
  self.action = action
24
24
  self.result = None
25
+ self.name = name
25
26
 
26
27
  def run(self) -> None:
27
28
  """
@@ -126,6 +126,6 @@ def configure_test_timeout(args: TestConfig) -> None:
126
126
 
127
127
  signal.signal(signal.SIGUSR1, timeout_handler)
128
128
 
129
- instance = WrappedThread(functools.partial(timeout_waiter, timeout_remaining.total_seconds()))
129
+ instance = WrappedThread(functools.partial(timeout_waiter, timeout_remaining.total_seconds()), 'Timeout Watchdog')
130
130
  instance.daemon = True
131
131
  instance.start()
@@ -533,16 +533,23 @@ def raw_command(
533
533
 
534
534
  try:
535
535
  try:
536
- cmd_bytes = [to_bytes(arg) for arg in cmd]
537
- env_bytes = dict((to_bytes(k), to_bytes(v)) for k, v in env.items())
538
- process = subprocess.Popen(cmd_bytes, env=env_bytes, stdin=stdin, stdout=stdout, stderr=stderr, cwd=cwd) # pylint: disable=consider-using-with
536
+ process = subprocess.Popen(cmd, env=env, stdin=stdin, stdout=stdout, stderr=stderr, cwd=cwd) # pylint: disable=consider-using-with
539
537
  except FileNotFoundError as ex:
540
538
  raise ApplicationError('Required program "%s" not found.' % cmd[0]) from ex
541
539
 
542
540
  if communicate:
543
541
  data_bytes = to_optional_bytes(data)
544
- stdout_bytes, stderr_bytes = communicate_with_process(process, data_bytes, stdout == subprocess.PIPE, stderr == subprocess.PIPE, capture=capture,
545
- output_stream=output_stream)
542
+
543
+ stdout_bytes, stderr_bytes = communicate_with_process(
544
+ name=cmd[0],
545
+ process=process,
546
+ stdin=data_bytes,
547
+ stdout=stdout == subprocess.PIPE,
548
+ stderr=stderr == subprocess.PIPE,
549
+ capture=capture,
550
+ output_stream=output_stream,
551
+ )
552
+
546
553
  stdout_text = to_optional_text(stdout_bytes, str_errors) or ''
547
554
  stderr_text = to_optional_text(stderr_bytes, str_errors) or ''
548
555
  else:
@@ -566,6 +573,7 @@ def raw_command(
566
573
 
567
574
 
568
575
  def communicate_with_process(
576
+ name: str,
569
577
  process: subprocess.Popen,
570
578
  stdin: t.Optional[bytes],
571
579
  stdout: bool,
@@ -583,16 +591,16 @@ def communicate_with_process(
583
591
  reader = OutputThread
584
592
 
585
593
  if stdin is not None:
586
- threads.append(WriterThread(process.stdin, stdin))
594
+ threads.append(WriterThread(process.stdin, stdin, name))
587
595
 
588
596
  if stdout:
589
- stdout_reader = reader(process.stdout, output_stream.get_buffer(sys.stdout.buffer))
597
+ stdout_reader = reader(process.stdout, output_stream.get_buffer(sys.stdout.buffer), name)
590
598
  threads.append(stdout_reader)
591
599
  else:
592
600
  stdout_reader = None
593
601
 
594
602
  if stderr:
595
- stderr_reader = reader(process.stderr, output_stream.get_buffer(sys.stderr.buffer))
603
+ stderr_reader = reader(process.stderr, output_stream.get_buffer(sys.stderr.buffer), name)
596
604
  threads.append(stderr_reader)
597
605
  else:
598
606
  stderr_reader = None
@@ -624,8 +632,8 @@ def communicate_with_process(
624
632
  class WriterThread(WrappedThread):
625
633
  """Thread to write data to stdin of a subprocess."""
626
634
 
627
- def __init__(self, handle: t.IO[bytes], data: bytes) -> None:
628
- super().__init__(self._run)
635
+ def __init__(self, handle: t.IO[bytes], data: bytes, name: str) -> None:
636
+ super().__init__(self._run, f'{self.__class__.__name__}: {name}')
629
637
 
630
638
  self.handle = handle
631
639
  self.data = data
@@ -642,8 +650,8 @@ class WriterThread(WrappedThread):
642
650
  class ReaderThread(WrappedThread, metaclass=abc.ABCMeta):
643
651
  """Thread to read stdout from a subprocess."""
644
652
 
645
- def __init__(self, handle: t.IO[bytes], buffer: t.BinaryIO) -> None:
646
- super().__init__(self._run)
653
+ def __init__(self, handle: t.IO[bytes], buffer: t.BinaryIO, name: str) -> None:
654
+ super().__init__(self._run, f'{self.__class__.__name__}: {name}')
647
655
 
648
656
  self.handle = handle
649
657
  self.buffer = buffer
@@ -19,7 +19,6 @@ if DESIRED_RLIMIT_NOFILE < CURRENT_RLIMIT_NOFILE:
19
19
 
20
20
  import base64
21
21
  import contextlib
22
- import errno
23
22
  import io
24
23
  import json
25
24
  import os
@@ -349,18 +348,13 @@ def remove_tree(path): # type: (str) -> None
349
348
  """Remove the specified directory tree."""
350
349
  try:
351
350
  shutil.rmtree(to_bytes(path))
352
- except OSError as ex:
353
- if ex.errno != errno.ENOENT:
354
- raise
351
+ except FileNotFoundError:
352
+ pass
355
353
 
356
354
 
357
355
  def make_dirs(path): # type: (str) -> None
358
356
  """Create a directory at path, including any necessary parent directories."""
359
- try:
360
- os.makedirs(to_bytes(path))
361
- except OSError as ex:
362
- if ex.errno != errno.EEXIST:
363
- raise
357
+ os.makedirs(to_bytes(path), exist_ok=True)
364
358
 
365
359
 
366
360
  def open_binary_file(path, mode='rb'): # type: (str, str) -> t.IO[bytes]