pyinfra 3.0b0__py2.py3-none-any.whl → 3.0b2__py2.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 (115) hide show
  1. pyinfra/api/__init__.py +3 -0
  2. pyinfra/api/arguments.py +12 -5
  3. pyinfra/api/arguments_typed.py +19 -6
  4. pyinfra/api/command.py +5 -3
  5. pyinfra/api/config.py +115 -13
  6. pyinfra/api/connectors.py +5 -2
  7. pyinfra/api/exceptions.py +19 -0
  8. pyinfra/api/facts.py +34 -33
  9. pyinfra/api/host.py +51 -12
  10. pyinfra/api/inventory.py +4 -0
  11. pyinfra/api/operation.py +88 -42
  12. pyinfra/api/operations.py +10 -11
  13. pyinfra/api/state.py +11 -2
  14. pyinfra/api/util.py +24 -16
  15. pyinfra/connectors/base.py +4 -7
  16. pyinfra/connectors/chroot.py +5 -6
  17. pyinfra/connectors/docker.py +13 -19
  18. pyinfra/connectors/dockerssh.py +5 -4
  19. pyinfra/connectors/local.py +7 -7
  20. pyinfra/connectors/ssh.py +46 -25
  21. pyinfra/connectors/terraform.py +9 -6
  22. pyinfra/connectors/util.py +7 -8
  23. pyinfra/connectors/vagrant.py +11 -10
  24. pyinfra/context.py +1 -0
  25. pyinfra/facts/apk.py +2 -0
  26. pyinfra/facts/apt.py +2 -0
  27. pyinfra/facts/brew.py +2 -0
  28. pyinfra/facts/bsdinit.py +2 -0
  29. pyinfra/facts/cargo.py +2 -0
  30. pyinfra/facts/choco.py +3 -1
  31. pyinfra/facts/deb.py +9 -4
  32. pyinfra/facts/dnf.py +2 -0
  33. pyinfra/facts/docker.py +2 -0
  34. pyinfra/facts/files.py +2 -0
  35. pyinfra/facts/gem.py +2 -0
  36. pyinfra/facts/gpg.py +2 -0
  37. pyinfra/facts/hardware.py +30 -22
  38. pyinfra/facts/launchd.py +2 -0
  39. pyinfra/facts/lxd.py +2 -0
  40. pyinfra/facts/mysql.py +12 -6
  41. pyinfra/facts/npm.py +1 -0
  42. pyinfra/facts/openrc.py +2 -0
  43. pyinfra/facts/pacman.py +6 -2
  44. pyinfra/facts/pip.py +2 -0
  45. pyinfra/facts/pkg.py +2 -0
  46. pyinfra/facts/pkgin.py +2 -0
  47. pyinfra/facts/postgres.py +168 -0
  48. pyinfra/facts/postgresql.py +5 -162
  49. pyinfra/facts/rpm.py +12 -9
  50. pyinfra/facts/server.py +10 -13
  51. pyinfra/facts/snap.py +2 -0
  52. pyinfra/facts/systemd.py +28 -10
  53. pyinfra/facts/upstart.py +2 -0
  54. pyinfra/facts/util/packaging.py +3 -2
  55. pyinfra/facts/vzctl.py +2 -0
  56. pyinfra/facts/xbps.py +2 -0
  57. pyinfra/facts/yum.py +2 -0
  58. pyinfra/facts/zypper.py +2 -0
  59. pyinfra/operations/apk.py +3 -1
  60. pyinfra/operations/apt.py +16 -18
  61. pyinfra/operations/brew.py +10 -8
  62. pyinfra/operations/bsdinit.py +5 -3
  63. pyinfra/operations/cargo.py +3 -1
  64. pyinfra/operations/choco.py +3 -1
  65. pyinfra/operations/dnf.py +15 -19
  66. pyinfra/operations/files.py +86 -69
  67. pyinfra/operations/gem.py +3 -1
  68. pyinfra/operations/git.py +18 -16
  69. pyinfra/operations/iptables.py +33 -25
  70. pyinfra/operations/launchd.py +5 -6
  71. pyinfra/operations/lxd.py +7 -4
  72. pyinfra/operations/mysql.py +57 -53
  73. pyinfra/operations/npm.py +8 -1
  74. pyinfra/operations/openrc.py +5 -3
  75. pyinfra/operations/pacman.py +4 -5
  76. pyinfra/operations/pip.py +16 -9
  77. pyinfra/operations/pkg.py +3 -1
  78. pyinfra/operations/pkgin.py +3 -1
  79. pyinfra/operations/postgres.py +349 -0
  80. pyinfra/operations/postgresql.py +18 -335
  81. pyinfra/operations/puppet.py +3 -1
  82. pyinfra/operations/python.py +7 -3
  83. pyinfra/operations/selinux.py +42 -16
  84. pyinfra/operations/server.py +48 -43
  85. pyinfra/operations/snap.py +3 -1
  86. pyinfra/operations/ssh.py +12 -10
  87. pyinfra/operations/systemd.py +13 -9
  88. pyinfra/operations/sysvinit.py +6 -4
  89. pyinfra/operations/upstart.py +5 -3
  90. pyinfra/operations/util/files.py +24 -16
  91. pyinfra/operations/util/packaging.py +53 -37
  92. pyinfra/operations/util/service.py +18 -13
  93. pyinfra/operations/vzctl.py +12 -10
  94. pyinfra/operations/xbps.py +3 -1
  95. pyinfra/operations/yum.py +14 -18
  96. pyinfra/operations/zypper.py +8 -9
  97. pyinfra/version.py +5 -2
  98. {pyinfra-3.0b0.dist-info → pyinfra-3.0b2.dist-info}/METADATA +31 -29
  99. pyinfra-3.0b2.dist-info/RECORD +163 -0
  100. {pyinfra-3.0b0.dist-info → pyinfra-3.0b2.dist-info}/WHEEL +1 -1
  101. pyinfra_cli/commands.py +3 -2
  102. pyinfra_cli/inventory.py +38 -19
  103. pyinfra_cli/main.py +2 -0
  104. pyinfra_cli/prints.py +27 -105
  105. pyinfra_cli/util.py +3 -1
  106. tests/test_api/test_api_deploys.py +5 -5
  107. tests/test_api/test_api_operations.py +5 -5
  108. tests/test_connectors/test_ssh.py +105 -0
  109. tests/test_connectors/test_terraform.py +11 -8
  110. tests/test_connectors/test_vagrant.py +6 -6
  111. pyinfra-3.0b0.dist-info/RECORD +0 -162
  112. pyinfra_cli/inventory_dsl.py +0 -23
  113. {pyinfra-3.0b0.dist-info → pyinfra-3.0b2.dist-info}/LICENSE.md +0 -0
  114. {pyinfra-3.0b0.dist-info → pyinfra-3.0b2.dist-info}/entry_points.txt +0 -0
  115. {pyinfra-3.0b0.dist-info → pyinfra-3.0b2.dist-info}/top_level.txt +0 -0
pyinfra/api/operation.py CHANGED
@@ -11,7 +11,7 @@ from functools import wraps
11
11
  from inspect import signature
12
12
  from io import StringIO
13
13
  from types import FunctionType
14
- from typing import Any, Callable, Generator, Iterator, Optional, cast
14
+ from typing import TYPE_CHECKING, Any, Callable, Generator, Iterator, Optional, cast
15
15
 
16
16
  from typing_extensions import ParamSpec
17
17
 
@@ -36,16 +36,19 @@ from .util import (
36
36
 
37
37
  op_meta_default = object()
38
38
 
39
+ if TYPE_CHECKING:
40
+ from pyinfra.connectors.util import CommandOutput
41
+
39
42
 
40
43
  class OperationMeta:
41
44
  _hash: str
42
45
 
43
- _combined_output_lines = None
46
+ _combined_output: Optional["CommandOutput"] = None
44
47
  _commands: Optional[list[Any]] = None
45
- _maybe_is_change: bool = False
48
+ _maybe_is_change: Optional[bool] = None
46
49
  _success: Optional[bool] = None
47
50
 
48
- def __init__(self, hash, is_change=False):
51
+ def __init__(self, hash, is_change: Optional[bool]):
49
52
  self._hash = hash
50
53
  self._maybe_is_change = is_change
51
54
 
@@ -57,7 +60,7 @@ class OperationMeta:
57
60
  if self._commands is not None:
58
61
  return (
59
62
  "OperationMeta(executed=True, "
60
- f"changed={self.did_change()}, hash={self._hash}, commands={len(self._commands)})"
63
+ f"success={self.did_succeed}, hash={self._hash}, commands={len(self._commands)})"
61
64
  )
62
65
  return (
63
66
  "OperationMeta(executed=False, "
@@ -69,13 +72,13 @@ class OperationMeta:
69
72
  self,
70
73
  success: bool,
71
74
  commands: list[Any],
72
- combined_output_lines,
75
+ combined_output: "CommandOutput",
73
76
  ) -> None:
74
77
  if self.is_complete():
75
78
  raise RuntimeError("Cannot complete an already complete operation")
76
79
  self._success = success
77
80
  self._commands = commands
78
- self._combined_output_lines = combined_output_lines
81
+ self._combined_output = combined_output
79
82
 
80
83
  def is_complete(self) -> bool:
81
84
  return self._success is not None
@@ -84,31 +87,57 @@ class OperationMeta:
84
87
  if not self.is_complete():
85
88
  raise RuntimeError("Cannot evaluate operation result before execution")
86
89
 
87
- def did_change(self) -> bool:
88
- self._raise_if_not_complete()
90
+ @property
91
+ def will_change(self) -> bool:
92
+ if self._maybe_is_change is not None:
93
+ return self._maybe_is_change
94
+
95
+ op_data = context.state.get_op_data_for_host(context.host, self._hash)
96
+ cmd_gen = op_data.command_generator
97
+ for _ in cmd_gen():
98
+ self._maybe_is_change = True
99
+ return True
100
+ self._maybe_is_change = False
101
+ return False
102
+
103
+ def _did_change(self) -> bool:
89
104
  return bool(self._success and len(self._commands or []) > 0)
90
105
 
91
- def did_succeed(self) -> bool:
92
- self._raise_if_not_complete()
106
+ @property
107
+ def did_change(self):
108
+ return context.host.when(self._did_change)
109
+
110
+ @property
111
+ def did_not_change(self):
112
+ return context.host.when(lambda: not self._did_change())
113
+
114
+ def did_succeed(self, _raise_if_not_complete=True) -> bool:
115
+ if _raise_if_not_complete:
116
+ self._raise_if_not_complete()
93
117
  return self._success is True
94
118
 
95
119
  def did_error(self) -> bool:
96
120
  self._raise_if_not_complete()
97
121
  return self._success is False
98
122
 
99
- # Output lines
100
- def _get_lines(self, types=("stdout", "stderr")):
101
- self._raise_if_not_complete()
102
- assert self._combined_output_lines is not None
103
- return [line for type_, line in self._combined_output_lines if type_ in types]
123
+ # TODO: deprecated, remove in v4
124
+ @property
125
+ def changed(self) -> bool:
126
+ if self.is_complete():
127
+ return self._did_change()
128
+ return self.will_change
104
129
 
105
130
  @property
106
- def stdout_lines(self):
107
- return self._get_lines(types=("stdout",))
131
+ def stdout_lines(self) -> list[str]:
132
+ self._raise_if_not_complete()
133
+ assert self._combined_output is not None
134
+ return self._combined_output.stdout_lines
108
135
 
109
136
  @property
110
- def stderr_lines(self):
111
- return self._get_lines(types=("stderr",))
137
+ def stderr_lines(self) -> list[str]:
138
+ self._raise_if_not_complete()
139
+ assert self._combined_output is not None
140
+ return self._combined_output.stderr_lines
112
141
 
113
142
  @property
114
143
  def stdout(self) -> str:
@@ -118,14 +147,6 @@ class OperationMeta:
118
147
  def stderr(self) -> str:
119
148
  return "\n".join(self.stderr_lines)
120
149
 
121
- # TODO: deprecated, remove in v4
122
- @property
123
- def changed(self) -> int:
124
- if not self.is_complete():
125
- logger.warning("Checking changed before execution can give unexpected results")
126
- return self._maybe_is_change
127
- return self.did_change()
128
-
129
150
 
130
151
  def add_op(state: State, op_func, *args, **kwargs):
131
152
  """
@@ -164,6 +185,8 @@ P = ParamSpec("P")
164
185
  def operation(
165
186
  is_idempotent: bool = True,
166
187
  idempotent_notice: Optional[str] = None,
188
+ is_deprecated: bool = False,
189
+ deprecated_for: Optional[str] = None,
167
190
  _set_in_op: bool = True,
168
191
  ) -> Callable[[Callable[P, Generator]], PyinfraOperation[P]]:
169
192
  """
@@ -175,6 +198,8 @@ def operation(
175
198
  def decorator(f: Callable[P, Generator]) -> PyinfraOperation[P]:
176
199
  f.is_idempotent = is_idempotent # type: ignore[attr-defined]
177
200
  f.idempotent_notice = idempotent_notice # type: ignore[attr-defined]
201
+ f.is_deprecated = is_deprecated # type: ignore[attr-defined]
202
+ f.deprecated_for = deprecated_for # type: ignore[attr-defined]
178
203
  return _wrap_operation(f, _set_in_op=_set_in_op)
179
204
 
180
205
  return decorator
@@ -192,6 +217,15 @@ def _wrap_operation(func: Callable[P, Generator], _set_in_op: bool = True) -> Py
192
217
  + "function to call the underlying operation."
193
218
  )
194
219
 
220
+ if func.is_deprecated: # type: ignore[attr-defined]
221
+ if func.deprecated_for: # type: ignore[attr-defined]
222
+ logger.warning(
223
+ f"The {get_operation_name_from_func(func)} operation is "
224
+ + f"deprecated, please use: {func.deprecated_for}", # type: ignore[attr-defined] # noqa
225
+ )
226
+ else:
227
+ logger.warning(f"The {get_operation_name_from_func(func)} operation is deprecated")
228
+
195
229
  # Configure operation
196
230
  #
197
231
  # Get the meta kwargs (globals that apply to all hosts)
@@ -217,22 +251,30 @@ def _wrap_operation(func: Callable[P, Generator], _set_in_op: bool = True) -> Py
217
251
  break
218
252
 
219
253
  if has_run:
220
- return OperationMeta(op_hash)
254
+ return OperationMeta(op_hash, is_change=False)
255
+
256
+ # Grab a reference to any *current* deploy data as this may change when
257
+ # we later evaluate the operation at runtime.This means we put back the
258
+ # expected deploy data.
259
+ current_deploy_data = host.current_deploy_data
221
260
 
222
261
  # "Run" operation - here we make a generator that will yield out actual commands to execute
223
262
  # and, if we're diff-ing, we then iterate the generator now to determine if any changes
224
263
  # *would* be made based on the *current* remote state.
225
264
 
226
265
  def command_generator() -> Iterator[PyinfraCommand]:
227
- # Check global _if_ argument function and do nothing if returns False
266
+ # Check global _if argument function and do nothing if returns False
228
267
  if state.is_executing:
229
- _if = global_arguments.get("_if")
230
- if _if and _if() is False:
268
+ _ifs = global_arguments.get("_if")
269
+ if isinstance(_ifs, list) and not all(_if() for _if in _ifs):
270
+ return
271
+ elif callable(_ifs) and not _ifs():
231
272
  return
232
273
 
233
274
  host.in_op = _set_in_op
234
275
  host.current_op_hash = op_hash
235
276
  host.current_op_global_arguments = global_arguments
277
+ host.current_op_deploy_data = current_deploy_data
236
278
 
237
279
  try:
238
280
  for command in func(*args, **kwargs):
@@ -243,10 +285,12 @@ def _wrap_operation(func: Callable[P, Generator], _set_in_op: bool = True) -> Py
243
285
  host.in_op = False
244
286
  host.current_op_hash = None
245
287
  host.current_op_global_arguments = None
288
+ host.current_op_deploy_data = None
246
289
 
247
- op_is_change = False
290
+ op_is_change = None
248
291
  if state.should_check_for_changes():
249
- for command in command_generator():
292
+ op_is_change = False
293
+ for _ in command_generator():
250
294
  op_is_change = True
251
295
  break
252
296
  else:
@@ -279,6 +323,15 @@ def _wrap_operation(func: Callable[P, Generator], _set_in_op: bool = True) -> Py
279
323
  return cast(PyinfraOperation[P], decorated_func)
280
324
 
281
325
 
326
+ def get_operation_name_from_func(func):
327
+ if func.__module__:
328
+ module_bits = func.__module__.split(".")
329
+ module_name = module_bits[-1]
330
+ return "{0}.{1}".format(module_name, func.__name__)
331
+ else:
332
+ return func.__name__
333
+
334
+
282
335
  def generate_operation_name(func, host, kwargs, global_arguments):
283
336
  # Generate an operation name if needed (Module/Operation format)
284
337
  name = global_arguments.get("name")
@@ -287,14 +340,7 @@ def generate_operation_name(func, host, kwargs, global_arguments):
287
340
  names = {name}
288
341
  else:
289
342
  add_args = True
290
-
291
- if func.__module__:
292
- module_bits = func.__module__.split(".")
293
- module_name = module_bits[-1]
294
- name = "{0}/{1}".format(module_name.title(), func.__name__.title())
295
- else:
296
- name = func.__name__
297
-
343
+ name = get_operation_name_from_func(func)
298
344
  names = {name}
299
345
 
300
346
  if host.current_deploy_name:
pyinfra/api/operations.py CHANGED
@@ -67,17 +67,16 @@ def _run_host_op(state: "State", host: "Host", op_hash: str) -> Optional[bool]:
67
67
  timeout = global_arguments.get("_timeout", 0)
68
68
 
69
69
  executor_kwarg_keys = CONNECTOR_ARGUMENT_KEYS
70
- base_connector_arguments: ConnectorArguments = (
71
- cast( # https://github.com/python/mypy/issues/10371
72
- ConnectorArguments,
73
- {key: global_arguments[key] for key in executor_kwarg_keys if key in global_arguments},
74
- )
70
+ # See: https://github.com/python/mypy/issues/10371
71
+ base_connector_arguments: ConnectorArguments = cast(
72
+ ConnectorArguments,
73
+ {key: global_arguments[key] for key in executor_kwarg_keys if key in global_arguments}, # type: ignore[literal-required] # noqa
75
74
  )
76
75
 
77
76
  did_error = False
78
77
  executed_commands = 0
79
78
  commands = []
80
- all_combined_output_lines: list[OutputLine] = []
79
+ all_output_lines: list[OutputLine] = []
81
80
 
82
81
  for command in op_data.command_generator():
83
82
  commands.append(command)
@@ -103,19 +102,19 @@ def _run_host_op(state: "State", host: "Host", op_hash: str) -> Optional[bool]:
103
102
  )
104
103
 
105
104
  elif isinstance(command, StringCommand):
106
- combined_output_lines = CommandOutput([])
105
+ output_lines = CommandOutput([])
107
106
  try:
108
- status, combined_output_lines = command.execute(
107
+ status, output_lines = command.execute(
109
108
  state,
110
109
  host,
111
110
  connector_arguments,
112
111
  )
113
112
  except (timeout_error, socket_error, SSHException) as e:
114
113
  log_host_command_error(host, e, timeout=timeout)
115
- all_combined_output_lines.extend(combined_output_lines)
114
+ all_output_lines.extend(output_lines)
116
115
  # If we failed and have not already printed the stderr, print it
117
116
  if status is False and not state.print_output:
118
- print_host_combined_output(host, combined_output_lines)
117
+ print_host_combined_output(host, output_lines)
119
118
 
120
119
  else:
121
120
  try:
@@ -170,7 +169,7 @@ def _run_host_op(state: "State", host: "Host", op_hash: str) -> Optional[bool]:
170
169
  op_data.operation_meta.set_complete(
171
170
  op_success,
172
171
  commands,
173
- all_combined_output_lines,
172
+ CommandOutput(all_output_lines),
174
173
  )
175
174
 
176
175
  return return_status
pyinfra/api/state.py CHANGED
@@ -344,10 +344,19 @@ class State:
344
344
  def get_results_for_host(self, host: "Host") -> StateHostResults:
345
345
  return self.results[host]
346
346
 
347
- def get_op_data_for_host(self, host: "Host", op_hash: str):
347
+ def get_op_data_for_host(
348
+ self,
349
+ host: "Host",
350
+ op_hash: str,
351
+ ) -> StateOperationHostData:
348
352
  return self.ops[host][op_hash]
349
353
 
350
- def set_op_data_for_host(self, host: "Host", op_hash: str, op_data: StateOperationHostData):
354
+ def set_op_data_for_host(
355
+ self,
356
+ host: "Host",
357
+ op_hash: str,
358
+ op_data: StateOperationHostData,
359
+ ):
351
360
  self.ops[host][op_hash] = op_data
352
361
 
353
362
  def activate_host(self, host: "Host"):
pyinfra/api/util.py CHANGED
@@ -109,16 +109,16 @@ def get_caller_frameinfo(frame_offset: int = 0):
109
109
 
110
110
 
111
111
  def get_operation_order_from_stack(state: "State"):
112
+
112
113
  stack_items = list(reversed(stack()))
113
114
 
115
+ i = 0
114
116
  # Find the *first* occurrence of our deploy file in the reversed stack
115
117
  if state.current_deploy_filename:
116
118
  for i, stack_item in enumerate(stack_items):
117
119
  frame = getframeinfo(stack_item[0])
118
120
  if frame.filename == state.current_deploy_filename:
119
121
  break
120
- else:
121
- i = 0
122
122
 
123
123
  # Now generate a list of line numbers *following that file*
124
124
  line_numbers = []
@@ -139,7 +139,7 @@ def get_operation_order_from_stack(state: "State"):
139
139
  return line_numbers
140
140
 
141
141
 
142
- def get_template(filename_or_io: str):
142
+ def get_template(filename_or_io: str | IO):
143
143
  """
144
144
  Gets a jinja2 ``Template`` object for the input filename or string, with caching
145
145
  based on the filename of the template, or the SHA1 of the input string.
@@ -301,19 +301,27 @@ def make_hash(obj):
301
301
  if isinstance(obj, int)
302
302
  # Constants - the values can change between hosts but we should still
303
303
  # group them under the same operation hash.
304
- else "_PYINFRA_CONSTANT"
305
- if obj in (True, False, None)
306
- # Plain strings
307
- else obj
308
- if isinstance(obj, str)
309
- # Objects with __name__s
310
- else obj.__name__
311
- if hasattr(obj, "__name__")
312
- # Objects with names
313
- else obj.name
314
- if hasattr(obj, "name")
315
- # Repr anything else
316
- else repr(obj)
304
+ else (
305
+ "_PYINFRA_CONSTANT"
306
+ if obj in (True, False, None)
307
+ # Plain strings
308
+ else (
309
+ obj
310
+ if isinstance(obj, str)
311
+ # Objects with __name__s
312
+ else (
313
+ obj.__name__
314
+ if hasattr(obj, "__name__")
315
+ # Objects with names
316
+ else (
317
+ obj.name
318
+ if hasattr(obj, "name")
319
+ # Repr anything else
320
+ else repr(obj)
321
+ )
322
+ )
323
+ )
324
+ )
317
325
  )
318
326
 
319
327
  return sha1_hash(hash_string)
@@ -84,7 +84,7 @@ class BaseConnector(abc.ABC):
84
84
 
85
85
  @staticmethod
86
86
  @abc.abstractmethod
87
- def make_names_data(id: str) -> Iterator[tuple[str, dict, list[str]]]:
87
+ def make_names_data(name: str) -> Iterator[tuple[str, dict, list[str]]]:
88
88
  """
89
89
  Generates hosts/data/groups information for inventory. This allows a
90
90
  single connector reference to generate multiple target hosts.
@@ -108,8 +108,7 @@ class BaseConnector(abc.ABC):
108
108
  print_output: bool,
109
109
  print_input: bool,
110
110
  **arguments: Unpack["ConnectorArguments"],
111
- ) -> tuple[bool, "CommandOutput"]:
112
- ...
111
+ ) -> tuple[bool, "CommandOutput"]: ...
113
112
 
114
113
  @abc.abstractmethod
115
114
  def put_file(
@@ -120,8 +119,7 @@ class BaseConnector(abc.ABC):
120
119
  print_output: bool = False,
121
120
  print_input: bool = False,
122
121
  **arguments: Unpack["ConnectorArguments"],
123
- ) -> bool:
124
- ...
122
+ ) -> bool: ...
125
123
 
126
124
  @abc.abstractmethod
127
125
  def get_file(
@@ -132,8 +130,7 @@ class BaseConnector(abc.ABC):
132
130
  print_output: bool = False,
133
131
  print_input: bool = False,
134
132
  **arguments: Unpack["ConnectorArguments"],
135
- ) -> bool:
136
- ...
133
+ ) -> bool: ...
137
134
 
138
135
  def check_can_rsync(self):
139
136
  raise NotImplementedError("This connector does not support rsync")
@@ -40,17 +40,17 @@ class ChrootConnector(BaseConnector):
40
40
  self.local = LocalConnector(state, host)
41
41
 
42
42
  @staticmethod
43
- def make_names_data(directory: Optional[str] = None):
44
- if not directory:
43
+ def make_names_data(name: Optional[str] = None):
44
+ if not name:
45
45
  raise InventoryError("No directory provided!")
46
46
 
47
47
  show_warning()
48
48
 
49
- yield "@chroot/{0}".format(directory), {
50
- "chroot_directory": "/{0}".format(directory.lstrip("/")),
49
+ yield "@chroot/{0}".format(name), {
50
+ "chroot_directory": "/{0}".format(name.lstrip("/")),
51
51
  }, ["@chroot"]
52
52
 
53
- def connect(self):
53
+ def connect(self) -> None:
54
54
  self.local.connect()
55
55
 
56
56
  chroot_directory = self.host.data.chroot_directory
@@ -65,7 +65,6 @@ class ChrootConnector(BaseConnector):
65
65
  raise ConnectError(e.args[0])
66
66
 
67
67
  self.host.connector_data["chroot_directory"] = chroot_directory
68
- return True
69
68
 
70
69
  def run_shell_command(
71
70
  self,
@@ -24,7 +24,7 @@ if TYPE_CHECKING:
24
24
  from pyinfra.api.state import State
25
25
 
26
26
 
27
- class ConnectorData(TypedDict, total=False):
27
+ class ConnectorData(TypedDict):
28
28
  docker_identifier: str
29
29
 
30
30
 
@@ -35,6 +35,7 @@ connector_data_meta: dict[str, DataMeta] = {
35
35
 
36
36
  def _find_start_docker_container(container_id) -> tuple[str, bool]:
37
37
  docker_info = local.shell("docker container inspect {0}".format(container_id))
38
+ assert isinstance(docker_info, str)
38
39
  docker_info = json.loads(docker_info)[0]
39
40
  if docker_info["State"]["Running"] is False:
40
41
  logger.info("Starting stopped container: {0}".format(container_id))
@@ -60,8 +61,10 @@ class DockerConnector(BaseConnector):
60
61
  The docker connector allows you to build Docker images or modify running
61
62
  Docker containers. You can pass either an image name or existing container ID:
62
63
 
63
- + Image - will create a new container from the image, execute operations against it, save into a new Docker image and remove the container
64
- + Existing container ID - will execute operations against the running container, leaving it running
64
+ + Image - will create a new container from the image, execute operations \
65
+ against it, save into a new Docker image and remove the container
66
+ + Existing container ID - will execute operations against the running \
67
+ container, leaving it running
65
68
 
66
69
  .. code:: shell
67
70
 
@@ -91,17 +94,17 @@ class DockerConnector(BaseConnector):
91
94
  self.local = LocalConnector(state, host)
92
95
 
93
96
  @staticmethod
94
- def make_names_data(identifier=None):
95
- if not identifier:
97
+ def make_names_data(name=None):
98
+ if not name:
96
99
  raise InventoryError("No docker base ID provided!")
97
100
 
98
101
  yield (
99
- "@docker/{0}".format(identifier),
100
- {"docker_identifier": identifier},
102
+ "@docker/{0}".format(name),
103
+ {"docker_identifier": name},
101
104
  ["@docker"],
102
105
  )
103
106
 
104
- def connect(self):
107
+ def connect(self) -> None:
105
108
  self.local.connect()
106
109
 
107
110
  docker_identifier = self.data["docker_identifier"]
@@ -113,8 +116,6 @@ class DockerConnector(BaseConnector):
113
116
  except PyinfraError:
114
117
  self.container_id = _start_docker_image(docker_identifier)
115
118
 
116
- return True
117
-
118
119
  def disconnect(self):
119
120
  container_id = self.container_id
120
121
 
@@ -264,17 +265,10 @@ class DockerConnector(BaseConnector):
264
265
  )
265
266
 
266
267
  # Load the temporary file and write it to our file or IO object
267
- with open(temp_filename, encoding="utf-8") as temp_f:
268
+ with open(temp_filename, "rb") as temp_f:
268
269
  with get_file_io(filename_or_io, "wb") as file_io:
269
270
  data = temp_f.read()
270
- data_bytes: bytes
271
-
272
- if isinstance(data, str):
273
- data_bytes = data.encode()
274
- else:
275
- data_bytes = data
276
-
277
- file_io.write(data_bytes)
271
+ file_io.write(data)
278
272
  finally:
279
273
  os.close(fd)
280
274
  os.remove(temp_filename)
@@ -30,7 +30,8 @@ class DockerSSHConnector(BaseConnector):
30
30
  """
31
31
  **Note**: this connector is in beta!
32
32
 
33
- The ``@dockerssh`` connector allows you to run commands on Docker containers on a remote machine.
33
+ The ``@dockerssh`` connector allows you to run commands on Docker containers \
34
+ on a remote machine.
34
35
 
35
36
  .. code:: shell
36
37
 
@@ -50,10 +51,10 @@ class DockerSSHConnector(BaseConnector):
50
51
  self.ssh = SSHConnector(state, host)
51
52
 
52
53
  @staticmethod
53
- def make_names_data(host_image_str):
54
+ def make_names_data(name):
54
55
  try:
55
- hostname, image = host_image_str.split(":", 1)
56
- except (AttributeError, ValueError): # failure to parse the host_image_str
56
+ hostname, image = name.split(":", 1)
57
+ except (AttributeError, ValueError): # failure to parse the name
57
58
  raise InventoryError("No ssh host or docker base image provided!")
58
59
 
59
60
  if not image:
@@ -1,5 +1,5 @@
1
1
  import os
2
- from distutils.spawn import find_executable
2
+ from shutil import which
3
3
  from tempfile import mkstemp
4
4
  from typing import TYPE_CHECKING, Tuple
5
5
 
@@ -25,7 +25,8 @@ if TYPE_CHECKING:
25
25
 
26
26
  class LocalConnector(BaseConnector):
27
27
  """
28
- The ``@local`` connector executes changes on the local machine using subprocesses. **This connector is only compatible with MacOS & Linux hosts**.
28
+ The ``@local`` connector executes changes on the local machine using
29
+ subprocesses. **This connector is only compatible with MacOS & Linux hosts**.
29
30
 
30
31
  Examples:
31
32
 
@@ -38,8 +39,8 @@ class LocalConnector(BaseConnector):
38
39
  handles_execution = True
39
40
 
40
41
  @staticmethod
41
- def make_names_data(_=None):
42
- if _ is not None:
42
+ def make_names_data(name=None):
43
+ if name is not None:
43
44
  raise InventoryError("Cannot have more than one @local")
44
45
 
45
46
  yield "@local", {}, ["@local"]
@@ -205,9 +206,8 @@ class LocalConnector(BaseConnector):
205
206
 
206
207
  return True
207
208
 
208
- @staticmethod
209
- def check_can_rsync(host):
210
- if not find_executable("rsync"):
209
+ def check_can_rsync(self):
210
+ if not which("rsync"):
211
211
  raise NotImplementedError("The `rsync` binary is not available on this system.")
212
212
 
213
213
  def rsync(