pyinfra 3.0b4__py2.py3-none-any.whl → 3.0.1__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 (54) hide show
  1. pyinfra/api/facts.py +7 -50
  2. pyinfra/api/util.py +1 -1
  3. pyinfra/connectors/ssh.py +3 -3
  4. pyinfra/connectors/sshuserclient/client.py +1 -1
  5. pyinfra/facts/apk.py +5 -2
  6. pyinfra/facts/apt.py +13 -7
  7. pyinfra/facts/brew.py +26 -13
  8. pyinfra/facts/bsdinit.py +7 -6
  9. pyinfra/facts/cargo.py +4 -3
  10. pyinfra/facts/choco.py +6 -4
  11. pyinfra/facts/deb.py +12 -5
  12. pyinfra/facts/dnf.py +9 -6
  13. pyinfra/facts/docker.py +13 -6
  14. pyinfra/facts/files.py +3 -3
  15. pyinfra/facts/gem.py +5 -2
  16. pyinfra/facts/git.py +14 -21
  17. pyinfra/facts/gpg.py +2 -1
  18. pyinfra/facts/hardware.py +17 -11
  19. pyinfra/facts/launchd.py +5 -2
  20. pyinfra/facts/lxd.py +6 -2
  21. pyinfra/facts/mysql.py +7 -6
  22. pyinfra/facts/npm.py +2 -1
  23. pyinfra/facts/openrc.py +6 -2
  24. pyinfra/facts/pacman.py +7 -3
  25. pyinfra/facts/pkg.py +3 -1
  26. pyinfra/facts/pkgin.py +5 -2
  27. pyinfra/facts/postgres.py +3 -1
  28. pyinfra/facts/rpm.py +12 -9
  29. pyinfra/facts/runit.py +4 -2
  30. pyinfra/facts/selinux.py +12 -4
  31. pyinfra/facts/server.py +80 -51
  32. pyinfra/facts/snap.py +6 -2
  33. pyinfra/facts/systemd.py +10 -5
  34. pyinfra/facts/sysvinit.py +2 -1
  35. pyinfra/facts/upstart.py +5 -2
  36. pyinfra/facts/vzctl.py +6 -4
  37. pyinfra/facts/xbps.py +5 -2
  38. pyinfra/facts/yum.py +8 -5
  39. pyinfra/facts/zypper.py +7 -4
  40. pyinfra/operations/apt.py +5 -0
  41. pyinfra/operations/selinux.py +1 -1
  42. pyinfra/operations/server.py +1 -1
  43. {pyinfra-3.0b4.dist-info → pyinfra-3.0.1.dist-info}/METADATA +1 -5
  44. {pyinfra-3.0b4.dist-info → pyinfra-3.0.1.dist-info}/RECORD +54 -53
  45. pyinfra_cli/__main__.py +2 -3
  46. pyinfra_cli/inventory.py +13 -11
  47. pyinfra_cli/main.py +10 -9
  48. tests/test_api/test_api_facts.py +2 -2
  49. tests/test_cli/test_cli.py +0 -1
  50. tests/test_cli/test_cli_inventory.py +66 -0
  51. {pyinfra-3.0b4.dist-info → pyinfra-3.0.1.dist-info}/LICENSE.md +0 -0
  52. {pyinfra-3.0b4.dist-info → pyinfra-3.0.1.dist-info}/WHEEL +0 -0
  53. {pyinfra-3.0b4.dist-info → pyinfra-3.0.1.dist-info}/entry_points.txt +0 -0
  54. {pyinfra-3.0b4.dist-info → pyinfra-3.0.1.dist-info}/top_level.txt +0 -0
pyinfra/facts/mysql.py CHANGED
@@ -60,9 +60,11 @@ class MysqlFactBase(FactBase):
60
60
  abstract = True
61
61
 
62
62
  mysql_command: str
63
- requires_command = "mysql"
64
63
  ignore_errors = False
65
64
 
65
+ def requires_command(self, *args, **kwargs) -> str:
66
+ return "mysql"
67
+
66
68
  def command(
67
69
  self,
68
70
  # Details for speaking to MySQL via `mysql` CLI via `mysql` CLI
@@ -70,7 +72,7 @@ class MysqlFactBase(FactBase):
70
72
  mysql_password=None,
71
73
  mysql_host=None,
72
74
  mysql_port=None,
73
- ):
75
+ ) -> StringCommand:
74
76
  return make_execute_mysql_command(
75
77
  self.mysql_command,
76
78
  ignore_errors=self.ignore_errors,
@@ -134,8 +136,7 @@ class MysqlUsers(MysqlFactBase):
134
136
  default = dict
135
137
  mysql_command = "SELECT * FROM mysql.user"
136
138
 
137
- @staticmethod
138
- def process(output):
139
+ def process(self, output):
139
140
  rows = parse_columns_and_rows(output, "\t")
140
141
 
141
142
  users = {}
@@ -194,7 +195,7 @@ class MysqlUserGrants(MysqlFactBase):
194
195
  # Ignore errors as SHOW GRANTS will error if the user does not exist
195
196
  ignore_errors = True
196
197
 
197
- def command(
198
+ def command( # type: ignore[override]
198
199
  self,
199
200
  user,
200
201
  hostname="localhost",
@@ -203,7 +204,7 @@ class MysqlUserGrants(MysqlFactBase):
203
204
  mysql_password=None,
204
205
  mysql_host=None,
205
206
  mysql_port=None,
206
- ):
207
+ ) -> StringCommand:
207
208
  self.mysql_command = 'SHOW GRANTS FOR "{0}"@"{1}"'.format(user, hostname)
208
209
 
209
210
  return super().command(
pyinfra/facts/npm.py CHANGED
@@ -21,7 +21,8 @@ class NpmPackages(FactBase):
21
21
 
22
22
  default = dict
23
23
 
24
- requires_command = "npm"
24
+ def requires_command(self, directory=None) -> str:
25
+ return "npm"
25
26
 
26
27
  def command(self, directory=None):
27
28
  if directory:
pyinfra/facts/openrc.py CHANGED
@@ -11,7 +11,6 @@ class OpenrcStatus(FactBase):
11
11
  """
12
12
 
13
13
  default = dict
14
- requires_command = "rc-status"
15
14
  regex = (
16
15
  r"\s+([a-zA-Z0-9\-_]+)"
17
16
  r"\s+\[\s+"
@@ -21,6 +20,9 @@ class OpenrcStatus(FactBase):
21
20
  r"\s+\]"
22
21
  )
23
22
 
23
+ def requires_command(self, runlevel="default") -> str:
24
+ return "rc-status"
25
+
24
26
  def command(self, runlevel="default"):
25
27
  return "rc-status {0}".format(runlevel)
26
28
 
@@ -41,7 +43,9 @@ class OpenrcEnabled(FactBase):
41
43
  """
42
44
 
43
45
  default = dict
44
- requires_command = "rc-update"
46
+
47
+ def requires_command(self, runlevel="default") -> str:
48
+ return "rc-update"
45
49
 
46
50
  def command(self, runlevel="default"):
47
51
  self.runlevel = runlevel
pyinfra/facts/pacman.py CHANGED
@@ -21,7 +21,8 @@ class PacmanUnpackGroup(FactBase):
21
21
  ]
22
22
  """
23
23
 
24
- requires_command = "pacman"
24
+ def requires_command(self, *args, **kwargs) -> str:
25
+ return "pacman"
25
26
 
26
27
  default = list
27
28
 
@@ -44,8 +45,11 @@ class PacmanPackages(FactBase):
44
45
  }
45
46
  """
46
47
 
47
- command = "pacman -Q"
48
- requires_command = "pacman"
48
+ def command(self) -> str:
49
+ return "pacman -Q"
50
+
51
+ def requires_command(self, *args, **kwargs) -> str:
52
+ return "pacman"
49
53
 
50
54
  default = dict
51
55
 
pyinfra/facts/pkg.py CHANGED
@@ -16,9 +16,11 @@ class PkgPackages(FactBase):
16
16
  }
17
17
  """
18
18
 
19
- command = "pkg info || pkg_info || true"
20
19
  regex = r"^([a-zA-Z0-9_\-\+]+)\-([0-9a-z\.]+)"
21
20
  default = dict
22
21
 
22
+ def command(self) -> str:
23
+ return "pkg info || pkg_info || true"
24
+
23
25
  def process(self, output):
24
26
  return parse_packages(self.regex, output)
pyinfra/facts/pkgin.py CHANGED
@@ -18,8 +18,11 @@ class PkginPackages(FactBase):
18
18
  }
19
19
  """
20
20
 
21
- command = "pkgin list"
22
- requires_command = "pkgin"
21
+ def command(self) -> str:
22
+ return "pkgin list"
23
+
24
+ def requires_command(self) -> str:
25
+ return "pkgin"
23
26
 
24
27
  default = dict
25
28
 
pyinfra/facts/postgres.py CHANGED
@@ -48,7 +48,9 @@ class PostgresFactBase(FactBase):
48
48
  abstract = True
49
49
 
50
50
  psql_command: str
51
- requires_command = "psql"
51
+
52
+ def requires_command(self, *args, **kwargs):
53
+ return "psql"
52
54
 
53
55
  def command(
54
56
  self,
pyinfra/facts/rpm.py CHANGED
@@ -22,8 +22,11 @@ class RpmPackages(FactBase):
22
22
  }
23
23
  """
24
24
 
25
- command = "rpm --queryformat {0} -qa".format(shlex.quote(rpm_query_format))
26
- requires_command = "rpm"
25
+ def command(self) -> str:
26
+ return "rpm --queryformat {0} -qa".format(shlex.quote(rpm_query_format))
27
+
28
+ def requires_command(self) -> str:
29
+ return "rpm"
27
30
 
28
31
  default = dict
29
32
 
@@ -43,9 +46,10 @@ class RpmPackage(FactBase):
43
46
  }
44
47
  """
45
48
 
46
- requires_command = "rpm"
49
+ def requires_command(self, package) -> str:
50
+ return "rpm"
47
51
 
48
- def command(self, package):
52
+ def command(self, package) -> str:
49
53
  return (
50
54
  "rpm --queryformat {0} -q {1} || "
51
55
  "! test -e {1} || "
@@ -69,18 +73,17 @@ class RpmPackageProvides(FactBase):
69
73
 
70
74
  default = list
71
75
 
72
- requires_command = "repoquery"
76
+ def requires_command(self, *args, **kwargs) -> str:
77
+ return "repoquery"
73
78
 
74
- @staticmethod
75
- def command(package):
79
+ def command(self, package):
76
80
  # Accept failure here (|| true) for invalid/unknown packages
77
81
  return "repoquery --queryformat {0} --whatprovides {1} || true".format(
78
82
  shlex.quote(rpm_query_format),
79
83
  shlex.quote(package),
80
84
  )
81
85
 
82
- @staticmethod
83
- def process(output):
86
+ def process(self, output):
84
87
  packages = []
85
88
 
86
89
  for line in output:
pyinfra/facts/runit.py CHANGED
@@ -18,10 +18,12 @@ class RunitStatus(FactBase):
18
18
  }
19
19
  """
20
20
 
21
- requires_command = "sv"
22
21
  default = dict
23
22
 
24
- def command(self, service=None, svdir="/var/service"):
23
+ def requires_command(self, *args, **kwargs) -> str:
24
+ return "sv"
25
+
26
+ def command(self, service=None, svdir="/var/service") -> str:
25
27
  if service is None:
26
28
  return (
27
29
  'export SVDIR="{0}" && '
pyinfra/facts/selinux.py CHANGED
@@ -14,7 +14,9 @@ class SEBoolean(FactBase):
14
14
  If ``boolean`` does not exist, ``SEBoolean`` returns the empty string.
15
15
  """
16
16
 
17
- requires_command = "getsebool"
17
+ def requires_command(self, boolean) -> str:
18
+ return "getsebool"
19
+
18
20
  default = str
19
21
 
20
22
  def command(self, boolean):
@@ -60,9 +62,11 @@ class FileContextMapping(FactBase):
60
62
  Note: This fact requires root privileges.
61
63
  """
62
64
 
63
- requires_command = "semanage"
64
65
  default = dict
65
66
 
67
+ def requires_command(self, target) -> str:
68
+ return "semanage"
69
+
66
70
  def command(self, target):
67
71
  return "set -o pipefail && semanage fcontext -n -l | (grep '^{0}' || true)".format(target)
68
72
 
@@ -87,11 +91,13 @@ class SEPorts(FactBase):
87
91
  }
88
92
  """
89
93
 
90
- requires_command = "semanage"
91
94
  default = dict
92
95
  # example output: amqp_port_t tcp 15672, 5671-5672 # noqa: SC100
93
96
  _regex = re.compile(r"^([\w_]+)\s+(\w+)\s+([\w\-,\s]+)$")
94
97
 
98
+ def requires_command(self) -> str:
99
+ return "semanage"
100
+
95
101
  def command(self):
96
102
  return "semanage port -ln"
97
103
 
@@ -122,9 +128,11 @@ class SEPort(FactBase):
122
128
  Note: ``policycoreutils-dev`` must be installed for this to work.
123
129
  """
124
130
 
125
- requires_command = "sepolicy"
126
131
  default = str
127
132
 
133
+ def requires_command(self, protocol, port) -> str:
134
+ return "sepolicy"
135
+
128
136
  def command(self, protocol, port):
129
137
  return "(sepolicy network -p {0} 2>/dev/null || true) | grep {1}".format(port, protocol)
130
138
 
pyinfra/facts/server.py CHANGED
@@ -22,7 +22,8 @@ class User(FactBase):
22
22
  Returns the name of the current user.
23
23
  """
24
24
 
25
- command = "echo $USER"
25
+ def command(self):
26
+ return "echo $USER"
26
27
 
27
28
 
28
29
  class Home(FactBase[Optional[str]]):
@@ -40,7 +41,8 @@ class Path(FactBase):
40
41
  Returns the path environment variable of the current user.
41
42
  """
42
43
 
43
- command = "echo $PATH"
44
+ def command(self):
45
+ return "echo $PATH"
44
46
 
45
47
 
46
48
  class TmpDir(FactBase):
@@ -48,7 +50,8 @@ class TmpDir(FactBase):
48
50
  Returns the temporary directory of the current server, if configured.
49
51
  """
50
52
 
51
- command = "echo $TMPDIR"
53
+ def command(self):
54
+ return "echo $TMPDIR"
52
55
 
53
56
 
54
57
  class Hostname(FactBase):
@@ -56,7 +59,8 @@ class Hostname(FactBase):
56
59
  Returns the current hostname of the server.
57
60
  """
58
61
 
59
- command = "uname -n"
62
+ def command(self):
63
+ return "uname -n"
60
64
 
61
65
 
62
66
  class Kernel(FactBase):
@@ -64,7 +68,8 @@ class Kernel(FactBase):
64
68
  Returns the kernel name according to ``uname``.
65
69
  """
66
70
 
67
- command = "uname -s"
71
+ def command(self):
72
+ return "uname -s"
68
73
 
69
74
 
70
75
  class KernelVersion(FactBase):
@@ -72,7 +77,8 @@ class KernelVersion(FactBase):
72
77
  Returns the kernel version according to ``uname``.
73
78
  """
74
79
 
75
- command = "uname -r"
80
+ def command(self):
81
+ return "uname -r"
76
82
 
77
83
 
78
84
  # Deprecated/renamed -> Kernel
@@ -84,7 +90,8 @@ class Os(FactBase[str]):
84
90
  This fact is deprecated/renamed, please use the ``server.Kernel`` fact.
85
91
  """
86
92
 
87
- command = "uname -s"
93
+ def command(self):
94
+ return "uname -s"
88
95
 
89
96
 
90
97
  # Deprecated/renamed -> KernelVersion
@@ -96,7 +103,8 @@ class OsVersion(FactBase[str]):
96
103
  This fact is deprecated/renamed, please use the ``server.KernelVersion`` fact.
97
104
  """
98
105
 
99
- command = "uname -r"
106
+ def command(self):
107
+ return "uname -r"
100
108
 
101
109
 
102
110
  class Arch(FactBase[str]):
@@ -106,7 +114,8 @@ class Arch(FactBase[str]):
106
114
 
107
115
  # ``uname -p`` is not portable and returns ``unknown`` on Debian.
108
116
  # ``uname -m`` works on most Linux and BSD systems.
109
- command = "uname -m"
117
+ def command(self):
118
+ return "uname -m"
110
119
 
111
120
 
112
121
  class Command(FactBase[str]):
@@ -121,12 +130,12 @@ class Command(FactBase[str]):
121
130
 
122
131
  class Which(FactBase[Optional[str]]):
123
132
  """
124
- Returns the path of a given command, if available.
133
+ Returns the path of a given command according to `command -v`, if available.
125
134
  """
126
135
 
127
136
  @staticmethod
128
137
  def command(command):
129
- return "which {0} || true".format(command)
138
+ return "command -v {0} || true".format(command)
130
139
 
131
140
 
132
141
  class Date(FactBase[datetime]):
@@ -134,12 +143,13 @@ class Date(FactBase[datetime]):
134
143
  Returns the current datetime on the server.
135
144
  """
136
145
 
137
- command = f"date +'{ISO_DATE_FORMAT}'"
138
146
  default = datetime.now
139
147
 
140
- @staticmethod
141
- def process(output) -> datetime:
142
- return datetime.strptime(output[0], ISO_DATE_FORMAT)
148
+ def command(self):
149
+ return f"date +'{ISO_DATE_FORMAT}'"
150
+
151
+ def process(self, output) -> datetime:
152
+ return datetime.strptime(list(output)[0], ISO_DATE_FORMAT)
143
153
 
144
154
 
145
155
  class MacosVersion(FactBase[str]):
@@ -147,8 +157,11 @@ class MacosVersion(FactBase[str]):
147
157
  Returns the installed MacOS version.
148
158
  """
149
159
 
150
- command = "sw_vers -productVersion"
151
- requires_command = "sw_vers"
160
+ def requires_command(self) -> str:
161
+ return "sw_vers"
162
+
163
+ def command(self):
164
+ return "sw_vers -productVersion"
152
165
 
153
166
 
154
167
  class MountsDict(TypedDict):
@@ -175,11 +188,12 @@ class Mounts(FactBase[Dict[str, MountsDict]]):
175
188
  }
176
189
  """
177
190
 
178
- command = "mount"
179
191
  default = dict
180
192
 
181
- @staticmethod
182
- def process(output) -> dict[str, MountsDict]:
193
+ def command(self):
194
+ return "mount"
195
+
196
+ def process(self, output) -> dict[str, MountsDict]:
183
197
  devices: dict[str, MountsDict] = {}
184
198
 
185
199
  for line in output:
@@ -224,11 +238,12 @@ class KernelModules(FactBase):
224
238
  }
225
239
  """
226
240
 
227
- command = "! test -f /proc/modules || cat /proc/modules"
241
+ def command(self):
242
+ return "! test -f /proc/modules || cat /proc/modules"
243
+
228
244
  default = dict
229
245
 
230
- @staticmethod
231
- def process(output):
246
+ def process(self, output):
232
247
  modules = {}
233
248
 
234
249
  for line in output:
@@ -264,11 +279,13 @@ class LsbRelease(FactBase):
264
279
  }
265
280
  """
266
281
 
267
- command = "lsb_release -ca"
268
- requires_command = "lsb_release"
282
+ def command(self):
283
+ return "lsb_release -ca"
269
284
 
270
- @staticmethod
271
- def process(output):
285
+ def requires_command(self):
286
+ return "lsb_release"
287
+
288
+ def process(self, output):
272
289
  items = {}
273
290
 
274
291
  for line in output:
@@ -307,14 +324,12 @@ class Sysctl(FactBase):
307
324
 
308
325
  default = dict
309
326
 
310
- @staticmethod
311
- def command(keys=None):
327
+ def command(self, keys=None):
312
328
  if keys is None:
313
329
  return "sysctl -a"
314
330
  return f"sysctl {' '.join(keys)}"
315
331
 
316
- @staticmethod
317
- def process(output):
332
+ def process(self, output):
318
333
  sysctls = {}
319
334
 
320
335
  for line in output:
@@ -347,7 +362,9 @@ class Groups(FactBase[List[str]]):
347
362
  Returns a list of groups on the system.
348
363
  """
349
364
 
350
- command = "cat /etc/group"
365
+ def command(self):
366
+ return "cat /etc/group"
367
+
351
368
  default = list
352
369
 
353
370
  def process(self, output) -> list[str]:
@@ -392,10 +409,10 @@ class Crontab(FactBase[Dict[str, CrontabDict]]):
392
409
 
393
410
  default = dict
394
411
 
395
- requires_command = "crontab"
412
+ def requires_command(self, user=None) -> str:
413
+ return "crontab"
396
414
 
397
- @staticmethod
398
- def command(user=None):
415
+ def command(self, user=None):
399
416
  if user:
400
417
  return "crontab -l -u {0} || true".format(user)
401
418
  return "crontab -l || true"
@@ -455,7 +472,9 @@ class Users(FactBase):
455
472
  }
456
473
  """
457
474
 
458
- command = """
475
+ def command(self):
476
+ return """
477
+
459
478
  for i in `cat /etc/passwd | cut -d: -f1`; do
460
479
  ENTRY=`grep ^$i: /etc/passwd`;
461
480
  LASTLOG_RAW=`(lastlog -u $i 2> /dev/null || lastlogin $i 2> /dev/null)`;
@@ -538,11 +557,12 @@ class LinuxDistribution(FactBase[LinuxDistributionDict]):
538
557
  }
539
558
  """
540
559
 
541
- command = (
542
- "cd /etc/ && for file in $(ls -pdL *-release | grep -v /); "
543
- 'do echo "/etc/${file}"; cat "/etc/${file}"; echo ---; '
544
- "done"
545
- )
560
+ def command(self) -> str:
561
+ return (
562
+ "cd /etc/ && for file in $(ls -pdL *-release | grep -v /); "
563
+ 'do echo "/etc/${file}"; cat "/etc/${file}"; echo ---; '
564
+ "done"
565
+ )
546
566
 
547
567
  name_to_pretty_name = {
548
568
  "alpine": "Alpine",
@@ -626,8 +646,7 @@ class LinuxName(ShortFactBase[str]):
626
646
 
627
647
  fact = LinuxDistribution
628
648
 
629
- @staticmethod
630
- def process_data(data):
649
+ def process_data(self, data) -> str:
631
650
  return data["name"]
632
651
 
633
652
 
@@ -646,8 +665,11 @@ class Selinux(FactBase[SelinuxDict]):
646
665
  }
647
666
  """
648
667
 
649
- command = "sestatus"
650
- requires_command = "sestatus"
668
+ def command(self):
669
+ return "sestatus"
670
+
671
+ def requires_command(self) -> str:
672
+ return "sestatus"
651
673
 
652
674
  @staticmethod
653
675
  def default() -> SelinuxDict:
@@ -673,7 +695,9 @@ class LinuxGui(FactBase[List[str]]):
673
695
  Returns a list of available Linux GUIs.
674
696
  """
675
697
 
676
- command = "ls /usr/bin/*session || true"
698
+ def command(self):
699
+ return "ls /usr/bin/*session || true"
700
+
677
701
  default = list
678
702
 
679
703
  known_gui_binaries = {
@@ -702,8 +726,7 @@ class HasGui(ShortFactBase[bool]):
702
726
 
703
727
  fact = LinuxGui
704
728
 
705
- @staticmethod
706
- def process_data(data) -> bool:
729
+ def process_data(self, data) -> bool:
707
730
  return len(data) > 0
708
731
 
709
732
 
@@ -716,8 +739,12 @@ class Locales(FactBase[List[str]]):
716
739
  ["C.UTF-8", "en_US.UTF-8"]
717
740
  """
718
741
 
719
- command = "locale -a"
720
- requires_command = "locale"
742
+ def command(self) -> str:
743
+ return "locale -a"
744
+
745
+ def requires_command(self) -> str:
746
+ return "locale"
747
+
721
748
  default = list
722
749
 
723
750
  def process(self, output) -> list[str]:
@@ -784,7 +811,9 @@ class SecurityLimits(FactBase):
784
811
  ]
785
812
  """
786
813
 
787
- command = "cat /etc/security/limits.conf"
814
+ def command(self):
815
+ return "cat /etc/security/limits.conf"
816
+
788
817
  default = list
789
818
 
790
819
  def process(self, output):
pyinfra/facts/snap.py CHANGED
@@ -7,7 +7,9 @@ from pyinfra.api import FactBase
7
7
 
8
8
  class SnapBaseFact(FactBase):
9
9
  abstract = True
10
- requires_command = "snap"
10
+
11
+ def requires_command(self, *args, **kwargs) -> str:
12
+ return "snap"
11
13
 
12
14
 
13
15
  class SnapPackage(SnapBaseFact):
@@ -64,7 +66,9 @@ class SnapPackages(SnapBaseFact):
64
66
  """
65
67
 
66
68
  default = list
67
- command = "snap list"
69
+
70
+ def command(self) -> str:
71
+ return "snap list"
68
72
 
69
73
  def process(self, output):
70
74
  # Discard header output line from snap list command
pyinfra/facts/systemd.py CHANGED
@@ -3,7 +3,7 @@ from __future__ import annotations
3
3
  import re
4
4
  from typing import Dict, Iterable
5
5
 
6
- from pyinfra.api import FactBase, FactTypeError, QuoteString, StringCommand
6
+ from pyinfra.api import FactBase, QuoteString, StringCommand
7
7
 
8
8
  # Valid unit names consist of a "name prefix" and a dot and a suffix specifying the unit type.
9
9
  # The "unit prefix" must consist of one or more valid characters
@@ -52,14 +52,21 @@ class SystemdStatus(FactBase[Dict[str, bool]]):
52
52
  }
53
53
  """
54
54
 
55
- requires_command = "systemctl"
55
+ def requires_command(self, *args, **kwargs) -> str:
56
+ return "systemctl"
56
57
 
57
58
  default = dict
58
59
 
59
60
  state_key = "SubState"
60
61
  state_values = ["running", "waiting", "exited"]
61
62
 
62
- def command(self, user_mode=False, machine=None, user_name=None, services=None):
63
+ def command(
64
+ self,
65
+ user_mode: bool = False,
66
+ machine: str | None = None,
67
+ user_name: str | None = None,
68
+ services: str | list[str] | None = None,
69
+ ) -> StringCommand:
63
70
  fact_cmd = _make_systemctl_cmd(
64
71
  user_mode=user_mode,
65
72
  machine=machine,
@@ -72,8 +79,6 @@ class SystemdStatus(FactBase[Dict[str, bool]]):
72
79
  service_strs = [QuoteString(services)]
73
80
  elif isinstance(services, Iterable):
74
81
  service_strs = [QuoteString(s) for s in services]
75
- else:
76
- raise FactTypeError(f"Invalid type passed for services argument: {type(services)}")
77
82
 
78
83
  return StringCommand(
79
84
  fact_cmd,
pyinfra/facts/sysvinit.py CHANGED
@@ -18,7 +18,8 @@ class InitdStatus(FactBase):
18
18
  http://refspecs.linuxbase.org/LSB_3.1.0/LSB-Core-generic/LSB-Core-generic/iniscrptact.html
19
19
  """
20
20
 
21
- command = """
21
+ def command(self) -> str:
22
+ return """
22
23
  for SERVICE in `ls /etc/init.d/`; do
23
24
  _=`cat /etc/init.d/$SERVICE | grep "### BEGIN INIT INFO"`
24
25
 
pyinfra/facts/upstart.py CHANGED
@@ -10,12 +10,15 @@ class UpstartStatus(FactBase):
10
10
  Returns a dict of name -> status for upstart managed services.
11
11
  """
12
12
 
13
- command = "initctl list"
14
- requires_command = "initctl"
13
+ def requires_command(self) -> str:
14
+ return "initctl"
15
15
 
16
16
  regex = r"^([a-z\-]+) [a-z]+\/([a-z]+)"
17
17
  default = dict
18
18
 
19
+ def command(self):
20
+ return "initctl list"
21
+
19
22
  def process(self, output):
20
23
  services = {}
21
24