pyinfra 2.9.2__py2.py3-none-any.whl → 3.0__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 (156) hide show
  1. pyinfra/api/__init__.py +3 -0
  2. pyinfra/api/arguments.py +265 -253
  3. pyinfra/api/arguments_typed.py +80 -0
  4. pyinfra/api/command.py +68 -53
  5. pyinfra/api/config.py +139 -32
  6. pyinfra/api/connect.py +1 -1
  7. pyinfra/api/connectors.py +7 -26
  8. pyinfra/api/deploy.py +21 -52
  9. pyinfra/api/exceptions.py +33 -8
  10. pyinfra/api/facts.py +102 -137
  11. pyinfra/api/host.py +150 -82
  12. pyinfra/api/inventory.py +21 -25
  13. pyinfra/api/operation.py +240 -198
  14. pyinfra/api/operations.py +102 -148
  15. pyinfra/api/state.py +137 -79
  16. pyinfra/api/util.py +79 -86
  17. pyinfra/connectors/base.py +147 -0
  18. pyinfra/connectors/chroot.py +160 -169
  19. pyinfra/connectors/docker.py +220 -237
  20. pyinfra/connectors/dockerssh.py +231 -253
  21. pyinfra/connectors/local.py +196 -208
  22. pyinfra/connectors/ssh.py +530 -613
  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 +211 -137
  27. pyinfra/connectors/vagrant.py +60 -53
  28. pyinfra/context.py +4 -2
  29. pyinfra/facts/apk.py +2 -0
  30. pyinfra/facts/apt.py +2 -0
  31. pyinfra/facts/brew.py +2 -0
  32. pyinfra/facts/bsdinit.py +2 -0
  33. pyinfra/facts/cargo.py +2 -0
  34. pyinfra/facts/choco.py +2 -0
  35. pyinfra/facts/deb.py +7 -2
  36. pyinfra/facts/dnf.py +2 -0
  37. pyinfra/facts/docker.py +19 -0
  38. pyinfra/facts/files.py +47 -32
  39. pyinfra/facts/gem.py +2 -0
  40. pyinfra/facts/git.py +3 -1
  41. pyinfra/facts/gpg.py +3 -1
  42. pyinfra/facts/hardware.py +34 -24
  43. pyinfra/facts/iptables.py +5 -3
  44. pyinfra/facts/launchd.py +2 -0
  45. pyinfra/facts/lxd.py +2 -0
  46. pyinfra/facts/mysql.py +13 -6
  47. pyinfra/facts/npm.py +1 -0
  48. pyinfra/facts/openrc.py +2 -0
  49. pyinfra/facts/pacman.py +6 -2
  50. pyinfra/facts/pip.py +2 -0
  51. pyinfra/facts/pkg.py +2 -0
  52. pyinfra/facts/pkgin.py +2 -0
  53. pyinfra/facts/postgres.py +168 -0
  54. pyinfra/facts/postgresql.py +6 -160
  55. pyinfra/facts/rpm.py +12 -9
  56. pyinfra/facts/runit.py +68 -0
  57. pyinfra/facts/selinux.py +3 -1
  58. pyinfra/facts/server.py +80 -36
  59. pyinfra/facts/snap.py +2 -0
  60. pyinfra/facts/systemd.py +31 -12
  61. pyinfra/facts/sysvinit.py +10 -10
  62. pyinfra/facts/upstart.py +2 -0
  63. pyinfra/facts/util/packaging.py +7 -4
  64. pyinfra/facts/vzctl.py +2 -0
  65. pyinfra/facts/xbps.py +2 -0
  66. pyinfra/facts/yum.py +2 -0
  67. pyinfra/facts/zypper.py +2 -0
  68. pyinfra/local.py +4 -5
  69. pyinfra/operations/apk.py +6 -4
  70. pyinfra/operations/apt.py +46 -65
  71. pyinfra/operations/brew.py +17 -22
  72. pyinfra/operations/bsdinit.py +9 -7
  73. pyinfra/operations/cargo.py +4 -2
  74. pyinfra/operations/choco.py +4 -2
  75. pyinfra/operations/dnf.py +19 -23
  76. pyinfra/operations/docker.py +339 -0
  77. pyinfra/operations/files.py +188 -386
  78. pyinfra/operations/gem.py +4 -2
  79. pyinfra/operations/git.py +24 -53
  80. pyinfra/operations/iptables.py +29 -35
  81. pyinfra/operations/launchd.py +6 -7
  82. pyinfra/operations/lxd.py +8 -13
  83. pyinfra/operations/mysql.py +62 -81
  84. pyinfra/operations/npm.py +9 -2
  85. pyinfra/operations/openrc.py +6 -4
  86. pyinfra/operations/pacman.py +7 -8
  87. pyinfra/operations/pip.py +25 -24
  88. pyinfra/operations/pkg.py +4 -2
  89. pyinfra/operations/pkgin.py +6 -4
  90. pyinfra/operations/postgres.py +349 -0
  91. pyinfra/operations/postgresql.py +18 -379
  92. pyinfra/operations/puppet.py +3 -1
  93. pyinfra/operations/python.py +8 -19
  94. pyinfra/operations/runit.py +182 -0
  95. pyinfra/operations/selinux.py +47 -44
  96. pyinfra/operations/server.py +111 -127
  97. pyinfra/operations/snap.py +4 -4
  98. pyinfra/operations/ssh.py +20 -33
  99. pyinfra/operations/systemd.py +19 -15
  100. pyinfra/operations/sysvinit.py +9 -16
  101. pyinfra/operations/upstart.py +9 -7
  102. pyinfra/operations/util/__init__.py +12 -0
  103. pyinfra/operations/util/docker.py +177 -0
  104. pyinfra/operations/util/files.py +24 -16
  105. pyinfra/operations/util/packaging.py +55 -57
  106. pyinfra/operations/util/service.py +39 -51
  107. pyinfra/operations/vzctl.py +12 -10
  108. pyinfra/operations/xbps.py +6 -4
  109. pyinfra/operations/yum.py +18 -22
  110. pyinfra/operations/zypper.py +12 -13
  111. pyinfra/version.py +5 -2
  112. {pyinfra-2.9.2.dist-info → pyinfra-3.0.dist-info}/METADATA +40 -41
  113. pyinfra-3.0.dist-info/RECORD +167 -0
  114. {pyinfra-2.9.2.dist-info → pyinfra-3.0.dist-info}/WHEEL +1 -1
  115. pyinfra-3.0.dist-info/entry_points.txt +11 -0
  116. pyinfra_cli/__main__.py +4 -3
  117. pyinfra_cli/commands.py +7 -2
  118. pyinfra_cli/exceptions.py +78 -42
  119. pyinfra_cli/inventory.py +40 -6
  120. pyinfra_cli/log.py +17 -3
  121. pyinfra_cli/main.py +133 -90
  122. pyinfra_cli/prints.py +95 -127
  123. pyinfra_cli/util.py +62 -29
  124. tests/test_api/test_api.py +2 -0
  125. tests/test_api/test_api_arguments.py +13 -13
  126. tests/test_api/test_api_deploys.py +28 -29
  127. tests/test_api/test_api_facts.py +60 -98
  128. tests/test_api/test_api_operations.py +101 -201
  129. tests/test_cli/test_cli.py +18 -49
  130. tests/test_cli/test_cli_deploy.py +11 -37
  131. tests/test_cli/test_cli_exceptions.py +50 -19
  132. tests/test_cli/util.py +1 -1
  133. tests/test_connectors/test_chroot.py +6 -6
  134. tests/test_connectors/test_docker.py +4 -4
  135. tests/test_connectors/test_dockerssh.py +38 -50
  136. tests/test_connectors/test_local.py +11 -12
  137. tests/test_connectors/test_ssh.py +105 -93
  138. tests/test_connectors/test_terraform.py +9 -15
  139. tests/test_connectors/test_util.py +24 -46
  140. tests/test_connectors/test_vagrant.py +7 -7
  141. pyinfra/api/operation.pyi +0 -117
  142. pyinfra/connectors/ansible.py +0 -171
  143. pyinfra/connectors/mech.py +0 -186
  144. pyinfra/connectors/pyinfrawinrmsession/__init__.py +0 -28
  145. pyinfra/connectors/winrm.py +0 -320
  146. pyinfra/facts/windows.py +0 -366
  147. pyinfra/facts/windows_files.py +0 -90
  148. pyinfra/operations/windows.py +0 -59
  149. pyinfra/operations/windows_files.py +0 -551
  150. pyinfra-2.9.2.dist-info/RECORD +0 -170
  151. pyinfra-2.9.2.dist-info/entry_points.txt +0 -14
  152. tests/test_connectors/test_ansible.py +0 -64
  153. tests/test_connectors/test_mech.py +0 -126
  154. tests/test_connectors/test_winrm.py +0 -76
  155. {pyinfra-2.9.2.dist-info → pyinfra-3.0.dist-info}/LICENSE.md +0 -0
  156. {pyinfra-2.9.2.dist-info → pyinfra-3.0.dist-info}/top_level.txt +0 -0
pyinfra/facts/systemd.py CHANGED
@@ -1,6 +1,9 @@
1
+ from __future__ import annotations
2
+
1
3
  import re
4
+ from typing import Dict, Iterable
2
5
 
3
- from pyinfra.api import FactBase
6
+ from pyinfra.api import FactBase, FactTypeError, QuoteString, StringCommand
4
7
 
5
8
  # Valid unit names consist of a "name prefix" and a dot and a suffix specifying the unit type.
6
9
  # The "unit prefix" must consist of one or more valid characters
@@ -21,21 +24,19 @@ SYSTEMD_UNIT_NAME_REGEX = (
21
24
 
22
25
  def _make_systemctl_cmd(user_mode=False, machine=None, user_name=None):
23
26
  # base command for normal and user mode
24
- systemctl_cmd = "systemctl --user" if user_mode else "systemctl"
27
+ systemctl_cmd = ["systemctl --user"] if user_mode else ["systemctl"]
25
28
 
26
29
  # add user and machine flag if given in args
27
30
  if machine is not None:
28
31
  if user_name is not None:
29
- machine_opt = "--machine={1}@{0}".format(machine, user_name)
32
+ systemctl_cmd.append("--machine={1}@{0}".format(machine, user_name))
30
33
  else:
31
- machine_opt = "--machine={0}".format(machine)
32
-
33
- systemctl_cmd = "{0} {1}".format(systemctl_cmd, machine_opt)
34
+ systemctl_cmd.append("--machine={0}".format(machine))
34
35
 
35
- return systemctl_cmd
36
+ return StringCommand(*systemctl_cmd)
36
37
 
37
38
 
38
- class SystemdStatus(FactBase):
39
+ class SystemdStatus(FactBase[Dict[str, bool]]):
39
40
  """
40
41
  Returns a dictionary map of systemd units to booleans indicating whether they are active.
41
42
 
@@ -58,17 +59,35 @@ class SystemdStatus(FactBase):
58
59
  state_key = "SubState"
59
60
  state_values = ["running", "waiting", "exited"]
60
61
 
61
- def command(self, user_mode=False, machine=None, user_name=None):
62
+ def command(self, user_mode=False, machine=None, user_name=None, services=None):
62
63
  fact_cmd = _make_systemctl_cmd(
63
64
  user_mode=user_mode,
64
65
  machine=machine,
65
66
  user_name=user_name,
66
67
  )
67
68
 
68
- return f"{fact_cmd} show --all --property Id --property {self.state_key} '*'"
69
+ if services is None:
70
+ service_strs = [QuoteString("*")]
71
+ elif isinstance(services, str):
72
+ service_strs = [QuoteString(services)]
73
+ elif isinstance(services, Iterable):
74
+ service_strs = [QuoteString(s) for s in services]
75
+ else:
76
+ raise FactTypeError(f"Invalid type passed for services argument: {type(services)}")
77
+
78
+ return StringCommand(
79
+ fact_cmd,
80
+ "show",
81
+ "--all",
82
+ "--property",
83
+ "Id",
84
+ "--property",
85
+ self.state_key,
86
+ *service_strs,
87
+ )
69
88
 
70
- def process(self, output):
71
- services = {}
89
+ def process(self, output) -> Dict[str, bool]:
90
+ services: Dict[str, bool] = {}
72
91
 
73
92
  current_unit = None
74
93
  for line in output:
pyinfra/facts/sysvinit.py CHANGED
@@ -1,4 +1,7 @@
1
+ from __future__ import annotations
2
+
1
3
  import re
4
+ from typing import Optional
2
5
 
3
6
  from pyinfra.api import FactBase
4
7
 
@@ -6,7 +9,7 @@ from pyinfra.api import FactBase
6
9
  class InitdStatus(FactBase):
7
10
  """
8
11
  Low level check for every /etc/init.d/* script. Unfortunately many of these
9
- mishehave and return exit status 0 while also displaying the help info/not
12
+ misbehave and return exit status 0 while also displaying the help info/not
10
13
  offering status support.
11
14
 
12
15
  Returns a dict of name -> status.
@@ -29,26 +32,23 @@ class InitdStatus(FactBase):
29
32
  regex = r"([a-zA-Z0-9\-]+)=([0-9]+)"
30
33
  default = dict
31
34
 
32
- def process(self, output):
33
- services = {}
35
+ def process(self, output) -> dict[str, Optional[bool]]:
36
+ services: dict[str, Optional[bool]] = {}
34
37
 
35
38
  for line in output:
36
39
  matches = re.match(self.regex, line)
37
40
  if matches:
38
- status = int(matches.group(2))
41
+ intstatus = int(matches.group(2))
42
+ status: Optional[bool] = None
39
43
 
40
44
  # Exit code 0 = OK/running
41
- if status == 0:
45
+ if intstatus == 0:
42
46
  status = True
43
47
 
44
48
  # Exit codes 1-3 = DOWN/not running
45
- elif status < 4:
49
+ elif intstatus < 4:
46
50
  status = False
47
51
 
48
- # Exit codes 4+ = unknown
49
- else:
50
- status = None
51
-
52
52
  services[matches.group(1)] = status
53
53
 
54
54
  return services
pyinfra/facts/upstart.py CHANGED
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  import re
2
4
 
3
5
  from pyinfra.api import FactBase
@@ -1,8 +1,11 @@
1
+ from __future__ import annotations
2
+
1
3
  import re
4
+ from typing import Iterable
2
5
 
3
6
 
4
- def parse_packages(regex, output):
5
- packages = {}
7
+ def parse_packages(regex: str, output: Iterable[str]) -> dict[str, set[str]]:
8
+ packages: dict[str, set[str]] = {}
6
9
 
7
10
  for line in output:
8
11
  matches = re.match(regex, line)
@@ -18,7 +21,7 @@ def parse_packages(regex, output):
18
21
  def _parse_yum_or_zypper_repositories(output):
19
22
  repos = []
20
23
 
21
- current_repo = {}
24
+ current_repo: dict[str, str] = {}
22
25
  for line in output:
23
26
  line = line.strip()
24
27
  if not line or line.startswith("#"):
@@ -32,7 +35,7 @@ def _parse_yum_or_zypper_repositories(output):
32
35
  current_repo["name"] = line[1:-1]
33
36
 
34
37
  if current_repo and "=" in line:
35
- key, value = line.split("=", 1)
38
+ key, value = re.split(r"\s*=\s*", line, maxsplit=1)
36
39
  current_repo[key] = value
37
40
 
38
41
  if current_repo:
pyinfra/facts/vzctl.py CHANGED
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  import json
2
4
 
3
5
  from pyinfra.api import FactBase
pyinfra/facts/xbps.py CHANGED
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from pyinfra.api import FactBase
2
4
 
3
5
  from .util.packaging import parse_packages
pyinfra/facts/yum.py CHANGED
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from pyinfra.api import FactBase
2
4
 
3
5
  from .util import make_cat_files_command
pyinfra/facts/zypper.py CHANGED
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from pyinfra.api import FactBase
2
4
 
3
5
  from .util import make_cat_files_command
pyinfra/local.py CHANGED
@@ -6,7 +6,7 @@ import pyinfra
6
6
  from pyinfra import config, host, logger, state
7
7
  from pyinfra.api.exceptions import PyinfraError
8
8
  from pyinfra.api.util import get_file_path
9
- from pyinfra.connectors.util import run_local_process, split_combined_output
9
+ from pyinfra.connectors.util import run_local_process
10
10
  from pyinfra.context import ctx_state
11
11
 
12
12
 
@@ -76,19 +76,18 @@ def shell(
76
76
  if print_input:
77
77
  click.echo("{0}>>> {1}".format(print_prefix, command), err=True)
78
78
 
79
- return_code, combined_output = run_local_process(
79
+ return_code, output = run_local_process(
80
80
  command,
81
81
  print_output=print_output,
82
82
  print_prefix=print_prefix,
83
83
  )
84
- stdout, stderr = split_combined_output(combined_output)
85
84
 
86
85
  if return_code > 0 and not ignore_errors:
87
86
  raise PyinfraError(
88
- "Local command failed: {0}\n{1}".format(command, stderr),
87
+ "Local command failed: {0}\n{1}".format(command, output.stderr),
89
88
  )
90
89
 
91
- all_stdout.extend(stdout)
90
+ all_stdout.extend(output.stdout_lines)
92
91
 
93
92
  if not splitlines:
94
93
  return "\n".join(all_stdout)
pyinfra/operations/apk.py CHANGED
@@ -2,6 +2,8 @@
2
2
  Manage apk packages.
3
3
  """
4
4
 
5
+ from __future__ import annotations
6
+
5
7
  from pyinfra import host
6
8
  from pyinfra.api import operation
7
9
  from pyinfra.facts.apk import ApkPackages
@@ -23,7 +25,7 @@ def upgrade(available: bool = False):
23
25
  yield "apk upgrade"
24
26
 
25
27
 
26
- _upgrade = upgrade # noqa: E305
28
+ _upgrade = upgrade._inner # noqa: E305
27
29
 
28
30
 
29
31
  @operation(is_idempotent=False)
@@ -35,12 +37,12 @@ def update():
35
37
  yield "apk update"
36
38
 
37
39
 
38
- _update = update # noqa: E305
40
+ _update = update._inner # noqa: E305
39
41
 
40
42
 
41
- @operation
43
+ @operation()
42
44
  def packages(
43
- packages=None,
45
+ packages: str | list[str] | None = None,
44
46
  present=True,
45
47
  latest=False,
46
48
  update=False,
pyinfra/operations/apt.py CHANGED
@@ -2,10 +2,12 @@
2
2
  Manage apt packages and repositories.
3
3
  """
4
4
 
5
- from datetime import datetime, timedelta
5
+ from __future__ import annotations
6
+
7
+ from datetime import timedelta
6
8
  from urllib.parse import urlparse
7
9
 
8
- from pyinfra import host, state
10
+ from pyinfra import host
9
11
  from pyinfra.api import OperationError, operation
10
12
  from pyinfra.facts.apt import AptKeys, AptSources, parse_apt_repo
11
13
  from pyinfra.facts.deb import DebPackage, DebPackages
@@ -19,7 +21,7 @@ from .util.packaging import ensure_packages
19
21
  APT_UPDATE_FILENAME = "/var/lib/apt/periodic/update-success-stamp"
20
22
 
21
23
 
22
- def noninteractive_apt(command, force=False):
24
+ def noninteractive_apt(command: str, force=False):
23
25
  args = ["DEBIAN_FRONTEND=noninteractive apt-get -y"]
24
26
 
25
27
  if force:
@@ -36,8 +38,8 @@ def noninteractive_apt(command, force=False):
36
38
  return " ".join(args)
37
39
 
38
40
 
39
- @operation
40
- def key(src=None, keyserver=None, keyid=None):
41
+ @operation()
42
+ def key(src: str | None = None, keyserver: str | None = None, keyid: str | list[str] | None = None):
41
43
  """
42
44
  Add apt gpg keys with ``apt-key``.
43
45
 
@@ -48,6 +50,11 @@ def key(src=None, keyserver=None, keyid=None):
48
50
  keyserver/id:
49
51
  These must be provided together.
50
52
 
53
+ .. warning::
54
+ ``apt-key`` is deprecated in Debian, it is recommended NOT to use this
55
+ operation and instead follow the instructions here:
56
+ https://wiki.debian.org/DebianRepository/UseThirdParty
57
+
51
58
  **Examples:**
52
59
 
53
60
  .. code:: python
@@ -78,10 +85,6 @@ def key(src=None, keyserver=None, keyid=None):
78
85
  yield "(wget -O - {0} || curl -sSLf {0}) | apt-key add -".format(src)
79
86
  else:
80
87
  yield "apt-key add {0}".format(src)
81
-
82
- if keyid:
83
- for kid in keyid:
84
- existing_keys[kid] = {}
85
88
  else:
86
89
  host.noop("All keys from {0} are already available in the apt keychain".format(src))
87
90
 
@@ -98,8 +101,6 @@ def key(src=None, keyserver=None, keyid=None):
98
101
  keyserver,
99
102
  " ".join(needed_keys),
100
103
  )
101
- for kid in keyid:
102
- existing_keys[kid] = {}
103
104
  else:
104
105
  host.noop(
105
106
  "Keys {0} are already available in the apt keychain".format(
@@ -108,8 +109,8 @@ def key(src=None, keyserver=None, keyid=None):
108
109
  )
109
110
 
110
111
 
111
- @operation
112
- def repo(src, present=True, filename=None):
112
+ @operation()
113
+ def repo(src: str, present=True, filename: str | None = None):
113
114
  """
114
115
  Add/remove apt repositories.
115
116
 
@@ -144,24 +145,20 @@ def repo(src, present=True, filename=None):
144
145
 
145
146
  # Doesn't exist and we want it
146
147
  if not is_present and present:
147
- yield from files.line(
148
- filename,
149
- src,
148
+ yield from files.line._inner(
149
+ path=filename,
150
+ line=src,
150
151
  escape_regex_characters=True,
151
152
  )
152
- apt_sources.append(repo)
153
153
 
154
154
  # Exists and we don't want it
155
155
  elif is_present and not present:
156
- yield from files.line(
157
- filename,
158
- src,
156
+ yield from files.line._inner(
157
+ path=filename,
158
+ line=src,
159
159
  present=False,
160
- assume_present=True,
161
160
  escape_regex_characters=True,
162
161
  )
163
- apt_sources.remove(repo)
164
-
165
162
  else:
166
163
  host.noop(
167
164
  'apt repo "{0}" {1}'.format(
@@ -172,7 +169,7 @@ def repo(src, present=True, filename=None):
172
169
 
173
170
 
174
171
  @operation(is_idempotent=False)
175
- def ppa(src, present=True):
172
+ def ppa(src: str, present=True):
176
173
  """
177
174
  Add/remove Ubuntu ppa repositories.
178
175
 
@@ -201,8 +198,8 @@ def ppa(src, present=True):
201
198
  yield 'apt-add-repository -y --remove "{0}"'.format(src)
202
199
 
203
200
 
204
- @operation
205
- def deb(src, present=True, force=False):
201
+ @operation()
202
+ def deb(src: str, present=True, force=False):
206
203
  """
207
204
  Add/remove ``.deb`` file packages.
208
205
 
@@ -234,27 +231,23 @@ def deb(src, present=True, force=False):
234
231
  # If source is a url
235
232
  if urlparse(src).scheme:
236
233
  # Generate a temp filename
237
- temp_filename = state.get_temp_filename(src)
234
+ temp_filename = host.get_temp_filename(src)
238
235
 
239
236
  # Ensure it's downloaded
240
- yield from files.download(src, temp_filename)
237
+ yield from files.download._inner(src=src, dest=temp_filename)
241
238
 
242
239
  # Override the source with the downloaded file
243
240
  src = temp_filename
244
241
 
245
242
  # Check for file .deb information (if file is present)
246
- info = host.get_fact(DebPackage, name=src)
243
+ info = host.get_fact(DebPackage, package=src)
247
244
  current_packages = host.get_fact(DebPackages)
248
245
 
249
246
  exists = False
250
247
 
251
248
  # We have deb info! Check against installed packages
252
- if info:
253
- if (
254
- info["name"] in current_packages
255
- and info.get("version") in current_packages[info["name"]]
256
- ):
257
- exists = True
249
+ if info and info.get("version") in current_packages.get(info.get("name"), {}):
250
+ exists = True
258
251
 
259
252
  # Package does not exist and we want?
260
253
  if present:
@@ -266,9 +259,6 @@ def deb(src, present=True, force=False):
266
259
  # Now reinstall, and critically configure, the package - if there are still
267
260
  # missing deps, now we error
268
261
  yield "dpkg --force-confdef --force-confold -i {0}".format(src)
269
-
270
- if info:
271
- current_packages[info["name"]] = [info.get("version")]
272
262
  else:
273
263
  host.noop("deb {0} is installed".format(original_src))
274
264
 
@@ -279,7 +269,6 @@ def deb(src, present=True, force=False):
279
269
  noninteractive_apt("remove", force=force),
280
270
  info["name"],
281
271
  )
282
- current_packages.pop(info["name"])
283
272
  else:
284
273
  host.noop("deb {0} is not installed".format(original_src))
285
274
 
@@ -291,7 +280,7 @@ def deb(src, present=True, force=False):
291
280
  "unless the ``cache_time`` argument is provided."
292
281
  ),
293
282
  )
294
- def update(cache_time=None):
283
+ def update(cache_time: int | None = None):
295
284
  """
296
285
  Updates apt repositories.
297
286
 
@@ -326,14 +315,6 @@ def update(cache_time=None):
326
315
  # cache_time to work.
327
316
  if cache_time:
328
317
  yield "touch {0}".format(APT_UPDATE_FILENAME)
329
- if cache_info is None:
330
- host.create_fact(
331
- File,
332
- kwargs={"path": APT_UPDATE_FILENAME},
333
- data={"mtime": datetime.utcnow()},
334
- )
335
- else:
336
- cache_info["mtime"] = datetime.utcnow()
337
318
 
338
319
 
339
320
  _update = update # noqa: E305
@@ -357,7 +338,7 @@ def upgrade(auto_remove: bool = False):
357
338
 
358
339
  # Upgrade all packages and remove unneeded transitive dependencies
359
340
  apt.upgrade(
360
- name="Upgrade apt packages and remove unneeded dependencies"
341
+ name="Upgrade apt packages and remove unneeded dependencies",
361
342
  auto_remove=True
362
343
  )
363
344
  """
@@ -390,19 +371,19 @@ def dist_upgrade():
390
371
  yield noninteractive_apt("dist-upgrade")
391
372
 
392
373
 
393
- @operation
374
+ @operation()
394
375
  def packages(
395
- packages=None,
376
+ packages: str | list[str] | None = None,
396
377
  present=True,
397
378
  latest=False,
398
379
  update=False,
399
- cache_time=None,
380
+ cache_time: int | None = None,
400
381
  upgrade=False,
401
382
  force=False,
402
383
  no_recommends=False,
403
384
  allow_downgrades=False,
404
- extra_install_args=None,
405
- extra_uninstall_args=None,
385
+ extra_install_args: str | None = None,
386
+ extra_uninstall_args: str | None = None,
406
387
  ):
407
388
  """
408
389
  Install/remove/update packages & update apt.
@@ -454,29 +435,29 @@ def packages(
454
435
  """
455
436
 
456
437
  if update:
457
- yield from _update(cache_time=cache_time)
438
+ yield from _update._inner(cache_time=cache_time)
458
439
 
459
440
  if upgrade:
460
- yield from _upgrade()
441
+ yield from _upgrade._inner()
461
442
 
462
- install_command = ["install"]
443
+ install_command_args = ["install"]
463
444
  if no_recommends is True:
464
- install_command.append("--no-install-recommends")
445
+ install_command_args.append("--no-install-recommends")
465
446
  if allow_downgrades:
466
- install_command.append("--allow-downgrades")
447
+ install_command_args.append("--allow-downgrades")
467
448
 
468
- upgrade_command = " ".join(install_command)
449
+ upgrade_command = " ".join(install_command_args)
469
450
 
470
451
  if extra_install_args:
471
- install_command.append(extra_install_args)
452
+ install_command_args.append(extra_install_args)
472
453
 
473
- install_command = " ".join(install_command)
454
+ install_command = " ".join(install_command_args)
474
455
 
475
- uninstall_command = ["remove"]
456
+ uninstall_command_args = ["remove"]
476
457
  if extra_uninstall_args:
477
- uninstall_command.append(extra_uninstall_args)
458
+ uninstall_command_args.append(extra_uninstall_args)
478
459
 
479
- uninstall_command = " ".join(uninstall_command)
460
+ uninstall_command = " ".join(uninstall_command_args)
480
461
 
481
462
  # Compare/ensure packages are present/not
482
463
  yield from ensure_packages(
@@ -2,7 +2,9 @@
2
2
  Manage brew packages on mac/OSX. See https://brew.sh/
3
3
  """
4
4
 
5
- import urllib
5
+ from __future__ import annotations
6
+
7
+ import urllib.parse
6
8
 
7
9
  from pyinfra import host
8
10
  from pyinfra.api import operation
@@ -35,9 +37,9 @@ def upgrade():
35
37
  _upgrade = upgrade # noqa: E305
36
38
 
37
39
 
38
- @operation
40
+ @operation()
39
41
  def packages(
40
- packages=None,
42
+ packages: str | list[str] | None = None,
41
43
  present=True,
42
44
  latest=False,
43
45
  update=False,
@@ -75,10 +77,10 @@ def packages(
75
77
  """
76
78
 
77
79
  if update:
78
- yield from _update()
80
+ yield from _update._inner()
79
81
 
80
82
  if upgrade:
81
- yield from _upgrade()
83
+ yield from _upgrade._inner()
82
84
 
83
85
  yield from ensure_packages(
84
86
  host,
@@ -93,27 +95,22 @@ def packages(
93
95
  )
94
96
 
95
97
 
96
- def cask_args(host):
98
+ def cask_args():
97
99
  return ("", " --cask") if new_cask_cli(host.get_fact(BrewVersion)) else ("cask ", "")
98
100
 
99
101
 
100
- @operation(
101
- is_idempotent=False,
102
- pipeline_facts={"brew_version": ""},
103
- )
102
+ @operation(is_idempotent=False)
104
103
  def cask_upgrade():
105
104
  """
106
105
  Upgrades all brew casks.
107
106
  """
108
107
 
109
- yield "brew %supgrade%s" % cask_args(host)
108
+ yield "brew %supgrade%s" % cask_args()
110
109
 
111
110
 
112
- @operation(
113
- pipeline_facts={"brew_version": ""},
114
- )
111
+ @operation()
115
112
  def casks(
116
- casks=None,
113
+ casks: str | list[str] | None = None,
117
114
  present=True,
118
115
  latest=False,
119
116
  upgrade=False,
@@ -143,9 +140,9 @@ def casks(
143
140
  """
144
141
 
145
142
  if upgrade:
146
- yield from cask_upgrade()
143
+ yield from cask_upgrade._inner()
147
144
 
148
- args = cask_args(host)
145
+ args = cask_args()
149
146
 
150
147
  yield from ensure_packages(
151
148
  host,
@@ -160,8 +157,8 @@ def casks(
160
157
  )
161
158
 
162
159
 
163
- @operation
164
- def tap(src=None, present=True, url=None):
160
+ @operation()
161
+ def tap(src: str | None = None, present=True, url: str | None = None):
165
162
  """
166
163
  Add/remove brew taps.
167
164
 
@@ -204,7 +201,7 @@ def tap(src=None, present=True, url=None):
204
201
  host.noop("no tap was specified")
205
202
  return
206
203
 
207
- src = src or urllib.parse.urlparse(url).path.strip("/")
204
+ src = src or str(urllib.parse.urlparse(url).path).strip("/")
208
205
 
209
206
  if len(src.split("/")) != 2:
210
207
  host.noop("src '{0}' doesn't have two components.".format(src))
@@ -219,7 +216,6 @@ def tap(src=None, present=True, url=None):
219
216
 
220
217
  if already_tapped:
221
218
  yield "brew untap {0}".format(src)
222
- taps.remove(src)
223
219
  return
224
220
 
225
221
  if not present:
@@ -232,5 +228,4 @@ def tap(src=None, present=True, url=None):
232
228
  cmd = " ".join([cmd, url])
233
229
 
234
230
  yield cmd
235
- taps.append(src)
236
231
  return
@@ -2,6 +2,8 @@
2
2
  Manage BSD init services (``/etc/rc.d``, ``/usr/local/etc/rc.d``).
3
3
  """
4
4
 
5
+ from __future__ import annotations
6
+
5
7
  from pyinfra import host
6
8
  from pyinfra.api import operation
7
9
  from pyinfra.facts.bsdinit import RcdStatus
@@ -11,14 +13,14 @@ from . import files
11
13
  from .util.service import handle_service_control
12
14
 
13
15
 
14
- @operation
16
+ @operation()
15
17
  def service(
16
- service,
18
+ service: str,
17
19
  running=True,
18
20
  restarted=False,
19
21
  reloaded=False,
20
- command=None,
21
- enabled=None,
22
+ command: str | None = None,
23
+ enabled: bool | None = None,
22
24
  ):
23
25
  """
24
26
  Manage the state of BSD init services.
@@ -49,9 +51,9 @@ def service(
49
51
 
50
52
  # BSD init is simple, just add/remove <service>_enabled="YES"
51
53
  if isinstance(enabled, bool):
52
- yield from files.line(
53
- "/etc/rc.conf.local",
54
- "^{0}_enable=".format(service),
54
+ yield from files.line._inner(
55
+ path="/etc/rc.conf.local",
56
+ line="^{0}_enable=".format(service),
55
57
  replace='{0}_enable="YES"'.format(service),
56
58
  present=enabled,
57
59
  )