pyinfra 2.9.2__py2.py3-none-any.whl → 3.0b1__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 (126) hide show
  1. pyinfra/api/__init__.py +3 -0
  2. pyinfra/api/arguments.py +261 -255
  3. pyinfra/api/arguments_typed.py +77 -0
  4. pyinfra/api/command.py +66 -53
  5. pyinfra/api/config.py +27 -22
  6. pyinfra/api/connect.py +1 -1
  7. pyinfra/api/connectors.py +2 -24
  8. pyinfra/api/deploy.py +21 -52
  9. pyinfra/api/exceptions.py +33 -8
  10. pyinfra/api/facts.py +77 -113
  11. pyinfra/api/host.py +150 -82
  12. pyinfra/api/inventory.py +17 -25
  13. pyinfra/api/operation.py +232 -198
  14. pyinfra/api/operations.py +102 -148
  15. pyinfra/api/state.py +137 -79
  16. pyinfra/api/util.py +55 -70
  17. pyinfra/connectors/base.py +150 -0
  18. pyinfra/connectors/chroot.py +160 -169
  19. pyinfra/connectors/docker.py +227 -237
  20. pyinfra/connectors/dockerssh.py +231 -253
  21. pyinfra/connectors/local.py +195 -207
  22. pyinfra/connectors/ssh.py +528 -615
  23. pyinfra/connectors/ssh_util.py +114 -0
  24. pyinfra/connectors/sshuserclient/client.py +5 -3
  25. pyinfra/connectors/terraform.py +86 -65
  26. pyinfra/connectors/util.py +212 -137
  27. pyinfra/connectors/vagrant.py +55 -48
  28. pyinfra/context.py +3 -2
  29. pyinfra/facts/docker.py +1 -0
  30. pyinfra/facts/files.py +45 -32
  31. pyinfra/facts/git.py +3 -1
  32. pyinfra/facts/gpg.py +1 -1
  33. pyinfra/facts/hardware.py +4 -2
  34. pyinfra/facts/iptables.py +5 -3
  35. pyinfra/facts/mysql.py +1 -0
  36. pyinfra/facts/postgres.py +168 -0
  37. pyinfra/facts/postgresql.py +5 -161
  38. pyinfra/facts/selinux.py +3 -1
  39. pyinfra/facts/server.py +77 -30
  40. pyinfra/facts/systemd.py +29 -12
  41. pyinfra/facts/sysvinit.py +10 -10
  42. pyinfra/facts/util/packaging.py +4 -2
  43. pyinfra/local.py +4 -5
  44. pyinfra/operations/apk.py +3 -3
  45. pyinfra/operations/apt.py +25 -47
  46. pyinfra/operations/brew.py +7 -14
  47. pyinfra/operations/bsdinit.py +4 -4
  48. pyinfra/operations/cargo.py +1 -1
  49. pyinfra/operations/choco.py +1 -1
  50. pyinfra/operations/dnf.py +4 -4
  51. pyinfra/operations/files.py +108 -321
  52. pyinfra/operations/gem.py +1 -1
  53. pyinfra/operations/git.py +6 -37
  54. pyinfra/operations/iptables.py +2 -10
  55. pyinfra/operations/launchd.py +1 -1
  56. pyinfra/operations/lxd.py +1 -9
  57. pyinfra/operations/mysql.py +5 -28
  58. pyinfra/operations/npm.py +1 -1
  59. pyinfra/operations/openrc.py +1 -1
  60. pyinfra/operations/pacman.py +3 -3
  61. pyinfra/operations/pip.py +14 -15
  62. pyinfra/operations/pkg.py +1 -1
  63. pyinfra/operations/pkgin.py +3 -3
  64. pyinfra/operations/postgres.py +347 -0
  65. pyinfra/operations/postgresql.py +17 -380
  66. pyinfra/operations/python.py +2 -17
  67. pyinfra/operations/selinux.py +5 -28
  68. pyinfra/operations/server.py +59 -84
  69. pyinfra/operations/snap.py +1 -3
  70. pyinfra/operations/ssh.py +8 -23
  71. pyinfra/operations/systemd.py +7 -7
  72. pyinfra/operations/sysvinit.py +3 -12
  73. pyinfra/operations/upstart.py +4 -4
  74. pyinfra/operations/util/__init__.py +12 -0
  75. pyinfra/operations/util/files.py +2 -2
  76. pyinfra/operations/util/packaging.py +6 -24
  77. pyinfra/operations/util/service.py +18 -37
  78. pyinfra/operations/vzctl.py +2 -2
  79. pyinfra/operations/xbps.py +3 -3
  80. pyinfra/operations/yum.py +4 -4
  81. pyinfra/operations/zypper.py +4 -4
  82. {pyinfra-2.9.2.dist-info → pyinfra-3.0b1.dist-info}/METADATA +19 -22
  83. pyinfra-3.0b1.dist-info/RECORD +163 -0
  84. pyinfra-3.0b1.dist-info/entry_points.txt +11 -0
  85. pyinfra_cli/__main__.py +2 -0
  86. pyinfra_cli/commands.py +7 -2
  87. pyinfra_cli/exceptions.py +83 -42
  88. pyinfra_cli/inventory.py +19 -4
  89. pyinfra_cli/log.py +17 -3
  90. pyinfra_cli/main.py +133 -90
  91. pyinfra_cli/prints.py +93 -129
  92. pyinfra_cli/util.py +60 -29
  93. tests/test_api/test_api.py +2 -0
  94. tests/test_api/test_api_arguments.py +13 -13
  95. tests/test_api/test_api_deploys.py +28 -29
  96. tests/test_api/test_api_facts.py +60 -98
  97. tests/test_api/test_api_operations.py +100 -200
  98. tests/test_cli/test_cli.py +18 -49
  99. tests/test_cli/test_cli_deploy.py +11 -37
  100. tests/test_cli/test_cli_exceptions.py +50 -19
  101. tests/test_cli/util.py +1 -1
  102. tests/test_connectors/test_chroot.py +6 -6
  103. tests/test_connectors/test_docker.py +4 -4
  104. tests/test_connectors/test_dockerssh.py +38 -50
  105. tests/test_connectors/test_local.py +11 -12
  106. tests/test_connectors/test_ssh.py +66 -107
  107. tests/test_connectors/test_terraform.py +9 -15
  108. tests/test_connectors/test_util.py +24 -46
  109. tests/test_connectors/test_vagrant.py +4 -4
  110. pyinfra/api/operation.pyi +0 -117
  111. pyinfra/connectors/ansible.py +0 -171
  112. pyinfra/connectors/mech.py +0 -186
  113. pyinfra/connectors/pyinfrawinrmsession/__init__.py +0 -28
  114. pyinfra/connectors/winrm.py +0 -320
  115. pyinfra/facts/windows.py +0 -366
  116. pyinfra/facts/windows_files.py +0 -90
  117. pyinfra/operations/windows.py +0 -59
  118. pyinfra/operations/windows_files.py +0 -551
  119. pyinfra-2.9.2.dist-info/RECORD +0 -170
  120. pyinfra-2.9.2.dist-info/entry_points.txt +0 -14
  121. tests/test_connectors/test_ansible.py +0 -64
  122. tests/test_connectors/test_mech.py +0 -126
  123. tests/test_connectors/test_winrm.py +0 -76
  124. {pyinfra-2.9.2.dist-info → pyinfra-3.0b1.dist-info}/LICENSE.md +0 -0
  125. {pyinfra-2.9.2.dist-info → pyinfra-3.0b1.dist-info}/WHEEL +0 -0
  126. {pyinfra-2.9.2.dist-info → pyinfra-3.0b1.dist-info}/top_level.txt +0 -0
pyinfra/api/command.py CHANGED
@@ -1,20 +1,21 @@
1
1
  import shlex
2
2
  from inspect import getfullargspec
3
3
  from string import Formatter
4
- from typing import TYPE_CHECKING
4
+ from typing import TYPE_CHECKING, Callable, Union
5
5
 
6
6
  import gevent
7
+ from typing_extensions import Unpack
7
8
 
8
9
  from pyinfra.context import ctx_config, ctx_host
9
10
 
10
- from .arguments import get_executor_kwarg_keys
11
+ from .arguments import ConnectorArguments
11
12
 
12
13
  if TYPE_CHECKING:
13
14
  from pyinfra.api.host import Host
14
15
  from pyinfra.api.state import State
15
16
 
16
17
 
17
- def make_formatted_string_command(string: str, *args, **kwargs):
18
+ def make_formatted_string_command(string: str, *args, **kwargs) -> "StringCommand":
18
19
  """
19
20
  Helper function that takes a shell command or script as a string, splits it
20
21
  using ``shlex.split`` and then formats each bit, returning a ``StringCommand``
@@ -50,39 +51,46 @@ class MaskString(str):
50
51
 
51
52
 
52
53
  class QuoteString:
53
- def __init__(self, obj):
54
- self.object = obj
54
+ obj: Union[str, "StringCommand"]
55
55
 
56
- def __repr__(self):
57
- return "QuoteString({0})".format(self.object)
56
+ def __init__(self, obj: Union[str, "StringCommand"]):
57
+ self.obj = obj
58
+
59
+ def __repr__(self) -> str:
60
+ return f"QuoteString({self.obj})"
58
61
 
59
62
 
60
63
  class PyinfraCommand:
61
- def __init__(self, *args, **kwargs):
62
- self.executor_kwargs = {
63
- key: kwargs[key] for key in get_executor_kwarg_keys() if key in kwargs
64
- }
64
+ connector_arguments: ConnectorArguments
65
+
66
+ def __init__(self, **arguments: Unpack[ConnectorArguments]):
67
+ self.connector_arguments = arguments
65
68
 
66
- def __eq__(self, other):
69
+ def __eq__(self, other) -> bool:
67
70
  if isinstance(other, self.__class__) and repr(self) == repr(other):
68
71
  return True
69
72
  return False
70
73
 
71
- def execute(self, state: "State", host: "Host", executor_kwargs):
74
+ def execute(self, state: "State", host: "Host", connector_arguments: ConnectorArguments):
72
75
  raise NotImplementedError
73
76
 
74
77
 
75
78
  class StringCommand(PyinfraCommand):
76
- def __init__(self, *bits, **kwargs):
77
- super().__init__(**kwargs)
79
+ def __init__(
80
+ self,
81
+ *bits,
82
+ _separator=" ",
83
+ **arguments: Unpack[ConnectorArguments],
84
+ ):
85
+ super().__init__(**arguments)
78
86
  self.bits = bits
79
- self.separator = kwargs.pop("_separator", " ")
87
+ self.separator = _separator
80
88
 
81
- def __str__(self):
89
+ def __str__(self) -> str:
82
90
  return self.get_masked_value()
83
91
 
84
- def __repr__(self):
85
- return "StringCommand({0})".format(self.get_masked_value())
92
+ def __repr__(self) -> str:
93
+ return f"StringCommand({self.get_masked_value()})"
86
94
 
87
95
  def _get_all_bits(self, bit_accessor):
88
96
  all_bits = []
@@ -91,7 +99,7 @@ class StringCommand(PyinfraCommand):
91
99
  quote = False
92
100
  if isinstance(bit, QuoteString):
93
101
  quote = True
94
- bit = bit.object
102
+ bit = bit.obj
95
103
 
96
104
  if isinstance(bit, StringCommand):
97
105
  bit = bit_accessor(bit)
@@ -106,14 +114,14 @@ class StringCommand(PyinfraCommand):
106
114
 
107
115
  return all_bits
108
116
 
109
- def get_raw_value(self):
117
+ def get_raw_value(self) -> str:
110
118
  return self.separator.join(
111
119
  self._get_all_bits(
112
120
  lambda bit: bit.get_raw_value(),
113
121
  ),
114
122
  )
115
123
 
116
- def get_masked_value(self):
124
+ def get_masked_value(self) -> str:
117
125
  return self.separator.join(
118
126
  [
119
127
  "***" if isinstance(bit, MaskString) else bit
@@ -121,20 +129,25 @@ class StringCommand(PyinfraCommand):
121
129
  ],
122
130
  )
123
131
 
124
- def execute(self, state: "State", host: "Host", executor_kwargs):
125
- executor_kwargs.update(self.executor_kwargs)
132
+ def execute(self, state: "State", host: "Host", connector_arguments: ConnectorArguments):
133
+ connector_arguments.update(self.connector_arguments)
126
134
 
127
135
  return host.run_shell_command(
128
136
  self,
129
137
  print_output=state.print_output,
130
138
  print_input=state.print_input,
131
- return_combined_output=True,
132
- **executor_kwargs,
139
+ **connector_arguments,
133
140
  )
134
141
 
135
142
 
136
143
  class FileUploadCommand(PyinfraCommand):
137
- def __init__(self, src: str, dest: str, remote_temp_filename=None, **kwargs):
144
+ def __init__(
145
+ self,
146
+ src: str,
147
+ dest: str,
148
+ remote_temp_filename=None,
149
+ **kwargs: Unpack[ConnectorArguments],
150
+ ):
138
151
  super().__init__(**kwargs)
139
152
  self.src = src
140
153
  self.dest = dest
@@ -143,8 +156,8 @@ class FileUploadCommand(PyinfraCommand):
143
156
  def __repr__(self):
144
157
  return "FileUploadCommand({0}, {1})".format(self.src, self.dest)
145
158
 
146
- def execute(self, state: "State", host: "Host", executor_kwargs):
147
- executor_kwargs.update(self.executor_kwargs)
159
+ def execute(self, state: "State", host: "Host", connector_arguments: ConnectorArguments):
160
+ connector_arguments.update(self.connector_arguments)
148
161
 
149
162
  return host.put_file(
150
163
  self.src,
@@ -152,12 +165,18 @@ class FileUploadCommand(PyinfraCommand):
152
165
  remote_temp_filename=self.remote_temp_filename,
153
166
  print_output=state.print_output,
154
167
  print_input=state.print_input,
155
- **executor_kwargs,
168
+ **connector_arguments,
156
169
  )
157
170
 
158
171
 
159
172
  class FileDownloadCommand(PyinfraCommand):
160
- def __init__(self, src: str, dest: str, remote_temp_filename=None, **kwargs):
173
+ def __init__(
174
+ self,
175
+ src: str,
176
+ dest: str,
177
+ remote_temp_filename=None,
178
+ **kwargs: Unpack[ConnectorArguments],
179
+ ):
161
180
  super().__init__(**kwargs)
162
181
  self.src = src
163
182
  self.dest = dest
@@ -166,8 +185,8 @@ class FileDownloadCommand(PyinfraCommand):
166
185
  def __repr__(self):
167
186
  return "FileDownloadCommand({0}, {1})".format(self.src, self.dest)
168
187
 
169
- def execute(self, state: "State", host: "Host", executor_kwargs):
170
- executor_kwargs.update(self.executor_kwargs)
188
+ def execute(self, state: "State", host: "Host", connector_arguments: ConnectorArguments):
189
+ connector_arguments.update(self.connector_arguments)
171
190
 
172
191
  return host.get_file(
173
192
  self.src,
@@ -175,12 +194,18 @@ class FileDownloadCommand(PyinfraCommand):
175
194
  remote_temp_filename=self.remote_temp_filename,
176
195
  print_output=state.print_output,
177
196
  print_input=state.print_input,
178
- **executor_kwargs,
197
+ **connector_arguments,
179
198
  )
180
199
 
181
200
 
182
201
  class FunctionCommand(PyinfraCommand):
183
- def __init__(self, function, args, func_kwargs, **kwargs):
202
+ def __init__(
203
+ self,
204
+ function: Callable,
205
+ args,
206
+ func_kwargs,
207
+ **kwargs: Unpack[ConnectorArguments],
208
+ ):
184
209
  super().__init__(**kwargs)
185
210
  self.function = function
186
211
  self.args = args
@@ -193,34 +218,22 @@ class FunctionCommand(PyinfraCommand):
193
218
  self.kwargs,
194
219
  )
195
220
 
196
- def execute(self, state: "State", host: "Host", executor_kwargs):
221
+ def execute(self, state: "State", host: "Host", connector_arguments: ConnectorArguments):
197
222
  argspec = getfullargspec(self.function)
198
223
  if "state" in argspec.args and "host" in argspec.args:
199
224
  return self.function(state, host, *self.args, **self.kwargs)
200
225
 
201
- # Note: we use try/except here to catch any exception inside the greenlet
202
- # and bubble that back out as a return value before re-raising outside.
203
- # This is because gevent dumps stack traces when raised within a greenlet
204
- # and we want to handle that ourselves.
205
226
  def execute_function():
206
227
  with ctx_config.use(state.config.copy()):
207
228
  with ctx_host.use(host):
208
- try:
209
- self.function(*self.args, **self.kwargs)
210
- except Exception as e:
211
- return e
212
- else:
213
- return True
229
+ self.function(*self.args, **self.kwargs)
214
230
 
215
231
  greenlet = gevent.spawn(execute_function)
216
- res = greenlet.get()
217
- if isinstance(res, Exception):
218
- raise res
219
- return res
232
+ return greenlet.get()
220
233
 
221
234
 
222
235
  class RsyncCommand(PyinfraCommand):
223
- def __init__(self, src: str, dest: str, flags, **kwargs):
236
+ def __init__(self, src: str, dest: str, flags, **kwargs: Unpack[ConnectorArguments]):
224
237
  super().__init__(**kwargs)
225
238
  self.src = src
226
239
  self.dest = dest
@@ -229,12 +242,12 @@ class RsyncCommand(PyinfraCommand):
229
242
  def __repr__(self):
230
243
  return "RsyncCommand({0}, {1}, {2})".format(self.src, self.dest, self.flags)
231
244
 
232
- def execute(self, state: "State", host: "Host", executor_kwargs):
245
+ def execute(self, state: "State", host: "Host", connector_arguments: ConnectorArguments):
233
246
  return host.rsync(
234
247
  self.src,
235
248
  self.dest,
236
249
  self.flags,
237
250
  print_output=state.print_output,
238
251
  print_input=state.print_input,
239
- **executor_kwargs,
252
+ **connector_arguments,
240
253
  )
pyinfra/api/config.py CHANGED
@@ -1,5 +1,7 @@
1
1
  from os import path
2
+ from typing import Optional
2
3
 
4
+ # TODO: move to importlib.resources
3
5
  from pkg_resources import Requirement, ResolutionError, parse_version, require
4
6
 
5
7
  from pyinfra import __version__, state
@@ -9,37 +11,40 @@ from .exceptions import PyinfraError
9
11
 
10
12
  class ConfigDefaults:
11
13
  # % of hosts which have to fail for all operations to stop
12
- FAIL_PERCENT = None
14
+ FAIL_PERCENT: Optional[int] = None
13
15
  # Seconds to timeout SSH connections
14
- CONNECT_TIMEOUT = 10
15
- # Temporary directory (on the remote side) to use for caching any files/downloads
16
- TEMP_DIR = "/tmp"
16
+ CONNECT_TIMEOUT: int = 10
17
+ # Temporary directory (on the remote side) to use for caching any files/downloads, the default
18
+ # None value first tries to load the hosts' temporary directory configured via "TMPDIR" env
19
+ # variable, falling back to DEFAULT_TEMP_DIR if not set.
20
+ TEMP_DIR: Optional[str] = None
21
+ DEFAULT_TEMP_DIR: str = "/tmp"
17
22
  # Gevent pool size (defaults to #of target hosts)
18
- PARALLEL = 0
23
+ PARALLEL: int = 0
19
24
  # Specify the required pyinfra version (using PEP 440 setuptools specifier)
20
- REQUIRE_PYINFRA_VERSION = None
25
+ REQUIRE_PYINFRA_VERSION: Optional[str] = None
21
26
  # Specify any required packages (either using PEP 440 or a requirements file)
22
27
  # Note: this can also include pyinfra potentially replacing REQUIRE_PYINFRA_VERSION
23
- REQUIRE_PACKAGES = None
28
+ REQUIRE_PACKAGES: Optional[str] = None
24
29
  # All these can be overridden inside individual operation calls:
25
30
  # Switch to this user (from ssh_user) using su before executing operations
26
- SU_USER = None
27
- USE_SU_LOGIN = False
28
- SU_SHELL = None
29
- PRESERVE_SU_ENV = False
31
+ SU_USER: Optional[str] = None
32
+ USE_SU_LOGIN: bool = False
33
+ SU_SHELL: bool = False
34
+ PRESERVE_SU_ENV: bool = False
30
35
  # Use sudo and optional user
31
- SUDO = False
32
- SUDO_USER = None
33
- PRESERVE_SUDO_ENV = False
34
- USE_SUDO_LOGIN = False
35
- USE_SUDO_PASSWORD = False
36
+ SUDO: bool = False
37
+ SUDO_USER: Optional[str] = None
38
+ PRESERVE_SUDO_ENV: bool = False
39
+ USE_SUDO_LOGIN: bool = False
40
+ SUDO_PASSWORD: Optional[str] = None
36
41
  # Use doas and optional user
37
- DOAS = False
38
- DOAS_USER = None
42
+ DOAS: bool = False
43
+ DOAS_USER: Optional[str] = None
39
44
  # Only show errors but don't count as failure
40
- IGNORE_ERRORS = False
45
+ IGNORE_ERRORS: bool = False
41
46
  # Shell to use to execute commands
42
- SHELL = "sh"
47
+ SHELL: str = "sh"
43
48
 
44
49
 
45
50
  config_defaults = {key: value for key, value in ConfigDefaults.__dict__.items() if key.isupper()}
@@ -51,7 +56,7 @@ def check_pyinfra_version(version: str):
51
56
  running_version = parse_version(__version__)
52
57
  required_versions = Requirement.parse("pyinfra{0}".format(version))
53
58
 
54
- if not required_versions.specifier.contains(running_version):
59
+ if running_version not in required_versions: # type: ignore[operator]
55
60
  raise PyinfraError(
56
61
  f"pyinfra version requirement not met (requires {version}, running {__version__})"
57
62
  )
@@ -64,7 +69,7 @@ def check_require_packages(requirements_config):
64
69
  if isinstance(requirements_config, (list, tuple)):
65
70
  requirements = requirements_config
66
71
  else:
67
- with open(path.join(state.cwd, requirements_config), encoding="utf-8") as f:
72
+ with open(path.join(state.cwd or "", requirements_config), encoding="utf-8") as f:
68
73
  requirements = [line.split("#egg=")[-1] for line in f.read().splitlines()]
69
74
 
70
75
  try:
pyinfra/api/connect.py CHANGED
@@ -36,7 +36,7 @@ def connect_all(state: "State"):
36
36
  # Raise any unexpected exception
37
37
  greenlet.get()
38
38
 
39
- if host.connection:
39
+ if host.connected:
40
40
  state.activate_host(host)
41
41
  else:
42
42
  failed_hosts.add(host)
pyinfra/api/connectors.py CHANGED
@@ -1,30 +1,8 @@
1
1
  import pkg_resources
2
2
 
3
3
 
4
- class BaseConnectorMeta:
5
- handles_execution = False
6
- keys_prefix = ""
7
-
8
- class DataKeys:
9
- pass
10
-
11
- @classmethod
12
- def keys(cls):
13
- class Keys:
14
- pass
15
-
16
- for key in cls.DataKeys.__dict__:
17
- if not key.startswith("_"):
18
- setattr(Keys, key, f"{cls.keys_prefix}_{key}")
19
-
20
- return Keys
21
-
22
-
23
4
  def _load_connector(entrypoint):
24
- connector = entrypoint.load()
25
- if not getattr(connector, "Meta", None):
26
- connector.Meta = BaseConnectorMeta
27
- return connector
5
+ return entrypoint.load()
28
6
 
29
7
 
30
8
  def get_all_connectors():
@@ -38,7 +16,7 @@ def get_execution_connectors():
38
16
  return {
39
17
  connector: connector_mod
40
18
  for connector, connector_mod in get_all_connectors().items()
41
- if connector_mod.Meta.handles_execution
19
+ if connector_mod.handles_execution
42
20
  }
43
21
 
44
22
 
pyinfra/api/deploy.py CHANGED
@@ -5,31 +5,24 @@ creation (eg pyinfra-openstack).
5
5
  """
6
6
 
7
7
  from functools import wraps
8
- from typing import TYPE_CHECKING, Any, Callable, Union
8
+ from typing import TYPE_CHECKING, Any, Callable, Optional, cast
9
+
10
+ from typing_extensions import ParamSpec
9
11
 
10
12
  import pyinfra
11
- from pyinfra import context, logger
13
+ from pyinfra import context
12
14
  from pyinfra.context import ctx_host, ctx_state
13
15
 
14
16
  from .arguments import pop_global_arguments
17
+ from .arguments_typed import PyinfraOperation
15
18
  from .exceptions import PyinfraError
16
19
  from .host import Host
17
- from .util import get_args_kwargs_spec, get_call_location, memoize
20
+ from .util import get_call_location
18
21
 
19
22
  if TYPE_CHECKING:
20
23
  from pyinfra.api.state import State
21
24
 
22
25
 
23
- @memoize
24
- def show_state_host_arguments_warning(call_location):
25
- logger.warning(
26
- (
27
- "{0}:\n\tLegacy deploy function detected! Deploys should no longer define "
28
- "`state` and `host` arguments."
29
- ).format(call_location),
30
- )
31
-
32
-
33
26
  def add_deploy(state: "State", deploy_func: Callable[..., Any], *args, **kwargs):
34
27
  """
35
28
  Prepare & add an deploy to pyinfra.state by executing it on all hosts.
@@ -58,61 +51,37 @@ def add_deploy(state: "State", deploy_func: Callable[..., Any], *args, **kwargs)
58
51
  deploy_func(*args, **kwargs)
59
52
 
60
53
 
61
- def deploy(func_or_name: Union[Callable[..., Any], str], data_defaults=None, _call_location=None):
54
+ P = ParamSpec("P")
55
+
56
+
57
+ def deploy(name: Optional[str] = None, data_defaults=None):
62
58
  """
63
59
  Decorator that takes a deploy function (normally from a pyinfra_* package)
64
60
  and wraps any operations called inside with any deploy-wide kwargs/data.
65
61
  """
66
62
 
67
- # If not decorating, return function with config attached
68
- if isinstance(func_or_name, str):
69
- name = func_or_name
70
-
71
- def decorator(f):
72
- f.deploy_name = name
73
- if data_defaults:
74
- f.deploy_data = data_defaults
75
- return deploy(f, _call_location=get_call_location())
76
-
77
- return decorator
63
+ def decorator(func: Callable[P, Any]) -> PyinfraOperation[P]:
64
+ func.deploy_name = name or func.__name__ # type: ignore[attr-defined]
65
+ if data_defaults:
66
+ func.deploy_data = data_defaults # type: ignore[attr-defined]
67
+ return _wrap_deploy(func)
78
68
 
79
- # Actually decorate!
80
- func = func_or_name
69
+ return decorator
81
70
 
82
- # Check whether an operation is "legacy" - ie contains state=None, host=None kwargs
83
- # TODO: remove this in v3
84
- is_legacy = False
85
- args, kwargs = get_args_kwargs_spec(func)
86
- if all(key in kwargs and kwargs[key] is None for key in ("state", "host")):
87
- show_state_host_arguments_warning(_call_location or get_call_location())
88
- is_legacy = True
89
- func.is_legacy = is_legacy # type: ignore
90
71
 
72
+ def _wrap_deploy(func: Callable[P, Any]) -> PyinfraOperation[P]:
91
73
  @wraps(func)
92
- def decorated_func(*args, **kwargs):
74
+ def decorated_func(*args: P.args, **kwargs: P.kwargs) -> Any:
93
75
  deploy_kwargs, _ = pop_global_arguments(kwargs)
94
76
 
95
- # If this is a legacy operation function (ie - state & host arg kwargs), ensure that state
96
- # and host are included as kwargs.
97
- if func.is_legacy:
98
- if "state" not in kwargs:
99
- kwargs["state"] = context.state
100
- if "host" not in kwargs:
101
- kwargs["host"] = context.host
102
- # If not legacy, pop off any state/host kwargs that may come from legacy @deploy functions
103
- else:
104
- kwargs.pop("state", None)
105
- kwargs.pop("host", None)
106
-
107
- # Name the deploy
108
- deploy_name = getattr(func, "deploy_name", func.__name__)
109
77
  deploy_data = getattr(func, "deploy_data", None)
110
78
 
111
79
  with context.host.deploy(
112
- name=deploy_name,
80
+ name=func.deploy_name, # type: ignore[attr-defined]
113
81
  kwargs=deploy_kwargs,
114
82
  data=deploy_data,
115
83
  ):
116
84
  return func(*args, **kwargs)
117
85
 
118
- return decorated_func
86
+ decorated_func._inner = func # type: ignore[attr-defined]
87
+ return cast(PyinfraOperation[P], decorated_func)
pyinfra/api/exceptions.py CHANGED
@@ -4,15 +4,28 @@ class PyinfraError(Exception):
4
4
  """
5
5
 
6
6
 
7
- class NoMoreHostsError(PyinfraError):
7
+ class ConnectError(PyinfraError):
8
8
  """
9
- Exception raised when pyinfra runs out of hosts (they all failed).
9
+ Exception raised when connecting fails.
10
10
  """
11
11
 
12
12
 
13
- class ConnectError(PyinfraError):
13
+ class FactError(PyinfraError):
14
14
  """
15
- Exception raised when connecting fails.
15
+ Exception raised during fact gathering staging if a fact is unable to
16
+ generate output/change state.
17
+ """
18
+
19
+
20
+ class FactTypeError(FactError, TypeError):
21
+ """
22
+ Exception raised when a fact is passed invalid argument types.
23
+ """
24
+
25
+
26
+ class FactValueError(FactError, ValueError):
27
+ """
28
+ Exception raised when a fact is passed invalid argument values.
16
29
  """
17
30
 
18
31
 
@@ -47,19 +60,31 @@ class InventoryError(PyinfraError):
47
60
  """
48
61
 
49
62
 
50
- class NoConnectorError(PyinfraError, TypeError):
63
+ class NoConnectorError(PyinfraError, ValueError):
51
64
  """
52
65
  Raised when a requested connector is missing.
53
66
  """
54
67
 
55
68
 
56
- class NoHostError(PyinfraError, TypeError):
69
+ class NoHostError(PyinfraError, KeyError):
57
70
  """
58
71
  Raised when an inventory is missing a host.
59
72
  """
60
73
 
61
74
 
62
- class NoGroupError(PyinfraError, TypeError):
75
+ class NoGroupError(PyinfraError, KeyError):
76
+ """
77
+ Raised when an inventory is missing a group.
78
+ """
79
+
80
+
81
+ class ConnectorDataTypeError(PyinfraError, TypeError):
82
+ """
83
+ Raised when host connector data has invalid types.
84
+ """
85
+
86
+
87
+ class ArgumentTypeError(PyinfraError, TypeError):
63
88
  """
64
- Raise when an inventory is missing a group.
89
+ Raised when global arguments are passed with invalid types.
65
90
  """