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
@@ -1,165 +1,11 @@
1
- from pyinfra.api import FactBase, MaskString, QuoteString, StringCommand
2
- from pyinfra.api.util import try_int
1
+ from __future__ import annotations
3
2
 
4
- from .util.databases import parse_columns_and_rows
3
+ from .postgres import PostgresDatabases, PostgresRoles
5
4
 
6
5
 
7
- def make_psql_command(
8
- database=None,
9
- user=None,
10
- password=None,
11
- host=None,
12
- port=None,
13
- executable="psql",
14
- ):
15
- target_bits = []
6
+ class PostgresqlRoles(PostgresRoles):
7
+ deprecated = True
16
8
 
17
- if password:
18
- target_bits.append(MaskString('PGPASSWORD="{0}"'.format(password)))
19
9
 
20
- target_bits.append(executable)
21
-
22
- if database:
23
- target_bits.append("-d {0}".format(database))
24
-
25
- if user:
26
- target_bits.append("-U {0}".format(user))
27
-
28
- if host:
29
- target_bits.append("-h {0}".format(host))
30
-
31
- if port:
32
- target_bits.append("-p {0}".format(port))
33
-
34
- return StringCommand(*target_bits)
35
-
36
-
37
- def make_execute_psql_command(command, **psql_kwargs):
38
- return StringCommand(
39
- make_psql_command(**psql_kwargs),
40
- "-Ac",
41
- QuoteString(command), # quote this whole item as a single shell argument
42
- )
43
-
44
-
45
- class PostgresqlFactBase(FactBase):
46
- abstract = True
47
-
48
- requires_command = "psql"
49
-
50
- def command(
51
- self,
52
- psql_user=None,
53
- psql_password=None,
54
- psql_host=None,
55
- psql_port=None,
56
- ):
57
- return make_execute_psql_command(
58
- self.psql_command,
59
- user=psql_user,
60
- password=psql_password,
61
- host=psql_host,
62
- port=psql_port,
63
- )
64
-
65
-
66
- class PostgresqlRoles(PostgresqlFactBase):
67
- """
68
- Returns a dict of PostgreSQL roles and data:
69
-
70
- .. code:: python
71
-
72
- {
73
- "pyinfra": {
74
- "super": true,
75
- "createrole": false,
76
- "createdb": false,
77
- ...
78
- },
79
- }
80
- """
81
-
82
- default = dict
83
- psql_command = "SELECT * FROM pg_catalog.pg_roles"
84
-
85
- def process(self, output):
86
- # Remove the last line of the output (row count)
87
- output = output[:-1]
88
- rows = parse_columns_and_rows(
89
- output,
90
- "|",
91
- # Remove the "rol" prefix on column names
92
- remove_column_prefix="rol",
93
- )
94
-
95
- users = {}
96
-
97
- for details in rows:
98
- for key, value in list(details.items()):
99
- if key in ("oid", "connlimit"):
100
- details[key] = try_int(value)
101
-
102
- if key in (
103
- "super",
104
- "inherit",
105
- "createrole",
106
- "createdb",
107
- "canlogin",
108
- "replication",
109
- "bypassrls",
110
- ):
111
- details[key] = value == "t"
112
-
113
- users[details.pop("name")] = details
114
-
115
- return users
116
-
117
-
118
- class PostgresqlDatabases(PostgresqlFactBase):
119
- """
120
- Returns a dict of PostgreSQL databases and metadata:
121
-
122
- .. code:: python
123
-
124
- {
125
- "pyinfra_stuff": {
126
- "encoding": "UTF8",
127
- "collate": "en_US.UTF-8",
128
- "ctype": "en_US.UTF-8",
129
- ...
130
- },
131
- }
132
- """
133
-
134
- default = dict
135
- psql_command = "SELECT pg_catalog.pg_encoding_to_char(encoding), * FROM pg_catalog.pg_database"
136
-
137
- def process(self, output):
138
- # Remove the last line of the output (row count)
139
- output = output[:-1]
140
- rows = parse_columns_and_rows(
141
- output,
142
- "|",
143
- # Remove the "dat" prefix on column names
144
- remove_column_prefix="dat",
145
- )
146
-
147
- databases = {}
148
-
149
- for details in rows:
150
- details["encoding"] = details.pop("pg_encoding_to_char")
151
-
152
- for key, value in list(details.items()):
153
- if key.endswith("id") or key in (
154
- "dba",
155
- "tablespace",
156
- "connlimit",
157
- ):
158
- details[key] = try_int(value)
159
-
160
- if key in ("istemplate", "allowconn"):
161
- details[key] = value == "t"
162
-
163
- databases[details.pop("name")] = details
164
-
165
- return databases
10
+ class PostgresqlDatabases(PostgresDatabases):
11
+ deprecated = True
pyinfra/facts/rpm.py CHANGED
@@ -1,4 +1,7 @@
1
+ from __future__ import annotations
2
+
1
3
  import re
4
+ import shlex
2
5
 
3
6
  from pyinfra.api import FactBase
4
7
 
@@ -19,7 +22,7 @@ class RpmPackages(FactBase):
19
22
  }
20
23
  """
21
24
 
22
- command = 'rpm --queryformat "{0}" -qa'.format(rpm_query_format)
25
+ command = "rpm --queryformat {0} -qa".format(shlex.quote(rpm_query_format))
23
26
  requires_command = "rpm"
24
27
 
25
28
  default = dict
@@ -42,12 +45,12 @@ class RpmPackage(FactBase):
42
45
 
43
46
  requires_command = "rpm"
44
47
 
45
- def command(self, name):
48
+ def command(self, package):
46
49
  return (
47
- 'rpm --queryformat "{0}" -q {1} || '
50
+ "rpm --queryformat {0} -q {1} || "
48
51
  "! test -e {1} || "
49
- 'rpm --queryformat "{0}" -qp {1} 2> /dev/null'
50
- ).format(rpm_query_format, name)
52
+ "rpm --queryformat {0} -qp {1} 2> /dev/null"
53
+ ).format(shlex.quote(rpm_query_format), shlex.quote(package))
51
54
 
52
55
  def process(self, output):
53
56
  for line in output:
@@ -69,11 +72,11 @@ class RpmPackageProvides(FactBase):
69
72
  requires_command = "repoquery"
70
73
 
71
74
  @staticmethod
72
- def command(name):
75
+ def command(package):
73
76
  # Accept failure here (|| true) for invalid/unknown packages
74
- return 'repoquery --queryformat "{0}" --whatprovides {1} || true'.format(
75
- rpm_query_format,
76
- name,
77
+ return "repoquery --queryformat {0} --whatprovides {1} || true".format(
78
+ shlex.quote(rpm_query_format),
79
+ shlex.quote(package),
77
80
  )
78
81
 
79
82
  @staticmethod
pyinfra/facts/runit.py ADDED
@@ -0,0 +1,68 @@
1
+ from pyinfra.api import FactBase
2
+
3
+
4
+ class RunitStatus(FactBase):
5
+ """
6
+ Returns a dict of name -> status for runit services.
7
+
8
+ + service: optionally check only for a single service
9
+ + svdir: alternative ``SVDIR``
10
+
11
+ .. code:: python
12
+
13
+ {
14
+ 'agetty-tty1': True, # service is running
15
+ 'dhcpcd': False, # service is down
16
+ 'wpa_supplicant': None, # service is managed, but not running or down,
17
+ # possibly in a fail state
18
+ }
19
+ """
20
+
21
+ requires_command = "sv"
22
+ default = dict
23
+
24
+ def command(self, service=None, svdir="/var/service"):
25
+ if service is None:
26
+ return (
27
+ 'export SVDIR="{0}" && '
28
+ 'cd "$SVDIR" && find * -maxdepth 0 -exec sv status {{}} + 2>/dev/null'
29
+ ).format(svdir)
30
+ else:
31
+ return 'SVDIR="{0}" sv status "{1}"'.format(svdir, service)
32
+
33
+ def process(self, output):
34
+ services = {}
35
+ for line in output:
36
+ statusstr, service, _ = line.split(sep=": ", maxsplit=2)
37
+ status = None
38
+
39
+ if statusstr == "run":
40
+ status = True
41
+ elif statusstr == "down":
42
+ status = False
43
+ # another observable state is "fail"
44
+ # report as ``None`` for now
45
+
46
+ services[service] = status
47
+
48
+ return services
49
+
50
+
51
+ class RunitManaged(FactBase):
52
+ """
53
+ Returns a set of all services managed by runit
54
+
55
+ + service: optionally check only for a single service
56
+ + svdir: alternative ``SVDIR``
57
+ """
58
+
59
+ default = set
60
+
61
+ def command(self, service=None, svdir="/var/service"):
62
+ if service is None:
63
+ return 'cd "{0}" && find -mindepth 1 -maxdepth 1 -type l -printf "%f\n"'.format(svdir)
64
+ else:
65
+ return 'cd "{0}" && test -h "{1}" && echo "{1}" || true'.format(svdir, service)
66
+
67
+ def process(self, output):
68
+ return set(output)
pyinfra/facts/selinux.py CHANGED
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  import re
2
4
  from collections import defaultdict
3
5
 
@@ -94,7 +96,7 @@ class SEPorts(FactBase):
94
96
  return "semanage port -ln"
95
97
 
96
98
  def process(self, output):
97
- labels = defaultdict(dict)
99
+ labels: dict[str, dict] = defaultdict(dict)
98
100
  for line in output:
99
101
  m = SEPorts._regex.match(line)
100
102
  if m is None: # something went wrong
pyinfra/facts/server.py CHANGED
@@ -1,11 +1,15 @@
1
+ from __future__ import annotations
2
+
1
3
  import os
2
4
  import re
3
5
  import shutil
4
6
  from datetime import datetime
5
7
  from tempfile import mkdtemp
8
+ from typing import Dict, List, Optional, Union
6
9
 
7
10
  from dateutil.parser import parse as parse_date
8
11
  from distro import distro
12
+ from typing_extensions import NotRequired, TypedDict
9
13
 
10
14
  from pyinfra.api import FactBase, ShortFactBase
11
15
  from pyinfra.api.util import try_int
@@ -21,12 +25,14 @@ class User(FactBase):
21
25
  command = "echo $USER"
22
26
 
23
27
 
24
- class Home(FactBase):
28
+ class Home(FactBase[Optional[str]]):
25
29
  """
26
- Returns the home directory of the current user.
30
+ Returns the home directory of the given user, or the current user if no user is given.
27
31
  """
28
32
 
29
- command = "echo $HOME"
33
+ @staticmethod
34
+ def command(user=""):
35
+ return f"echo ~{user}"
30
36
 
31
37
 
32
38
  class Path(FactBase):
@@ -37,6 +43,14 @@ class Path(FactBase):
37
43
  command = "echo $PATH"
38
44
 
39
45
 
46
+ class TmpDir(FactBase):
47
+ """
48
+ Returns the temporary directory of the current server, if configured.
49
+ """
50
+
51
+ command = "echo $TMPDIR"
52
+
53
+
40
54
  class Hostname(FactBase):
41
55
  """
42
56
  Returns the current hostname of the server.
@@ -62,7 +76,7 @@ class KernelVersion(FactBase):
62
76
 
63
77
 
64
78
  # Deprecated/renamed -> Kernel
65
- class Os(FactBase):
79
+ class Os(FactBase[str]):
66
80
  """
67
81
  Returns the OS name according to ``uname``.
68
82
 
@@ -74,7 +88,7 @@ class Os(FactBase):
74
88
 
75
89
 
76
90
  # Deprecated/renamed -> KernelVersion
77
- class OsVersion(FactBase):
91
+ class OsVersion(FactBase[str]):
78
92
  """
79
93
  Returns the OS version according to ``uname``.
80
94
 
@@ -85,7 +99,7 @@ class OsVersion(FactBase):
85
99
  command = "uname -r"
86
100
 
87
101
 
88
- class Arch(FactBase):
102
+ class Arch(FactBase[str]):
89
103
  """
90
104
  Returns the system architecture according to ``uname``.
91
105
  """
@@ -95,7 +109,7 @@ class Arch(FactBase):
95
109
  command = "uname -m"
96
110
 
97
111
 
98
- class Command(FactBase):
112
+ class Command(FactBase[str]):
99
113
  """
100
114
  Returns the raw output lines of a given command.
101
115
  """
@@ -105,7 +119,7 @@ class Command(FactBase):
105
119
  return command
106
120
 
107
121
 
108
- class Which(FactBase):
122
+ class Which(FactBase[Optional[str]]):
109
123
  """
110
124
  Returns the path of a given command, if available.
111
125
  """
@@ -115,7 +129,7 @@ class Which(FactBase):
115
129
  return "which {0} || true".format(command)
116
130
 
117
131
 
118
- class Date(FactBase):
132
+ class Date(FactBase[datetime]):
119
133
  """
120
134
  Returns the current datetime on the server.
121
135
  """
@@ -124,11 +138,11 @@ class Date(FactBase):
124
138
  default = datetime.now
125
139
 
126
140
  @staticmethod
127
- def process(output):
141
+ def process(output) -> datetime:
128
142
  return datetime.strptime(output[0], ISO_DATE_FORMAT)
129
143
 
130
144
 
131
- class MacosVersion(FactBase):
145
+ class MacosVersion(FactBase[str]):
132
146
  """
133
147
  Returns the installed MacOS version.
134
148
  """
@@ -137,7 +151,13 @@ class MacosVersion(FactBase):
137
151
  requires_command = "sw_vers"
138
152
 
139
153
 
140
- class Mounts(FactBase):
154
+ class MountsDict(TypedDict):
155
+ device: str
156
+ type: str
157
+ options: list[str]
158
+
159
+
160
+ class Mounts(FactBase[Dict[str, MountsDict]]):
141
161
  """
142
162
  Returns a dictionary of mounted filesystems and information.
143
163
 
@@ -159,8 +179,8 @@ class Mounts(FactBase):
159
179
  default = dict
160
180
 
161
181
  @staticmethod
162
- def process(output):
163
- devices = {}
182
+ def process(output) -> dict[str, MountsDict]:
183
+ devices: dict[str, MountsDict] = {}
164
184
 
165
185
  for line in output:
166
186
  is_map = False
@@ -285,9 +305,14 @@ class Sysctl(FactBase):
285
305
  }
286
306
  """
287
307
 
288
- command = "sysctl -a"
289
308
  default = dict
290
309
 
310
+ @staticmethod
311
+ def command(keys=None):
312
+ if keys is None:
313
+ return "sysctl -a"
314
+ return f"sysctl {' '.join(keys)}"
315
+
291
316
  @staticmethod
292
317
  def process(output):
293
318
  sysctls = {}
@@ -317,7 +342,7 @@ class Sysctl(FactBase):
317
342
  return sysctls
318
343
 
319
344
 
320
- class Groups(FactBase):
345
+ class Groups(FactBase[List[str]]):
321
346
  """
322
347
  Returns a list of groups on the system.
323
348
  """
@@ -325,9 +350,8 @@ class Groups(FactBase):
325
350
  command = "cat /etc/group"
326
351
  default = list
327
352
 
328
- @staticmethod
329
- def process(output):
330
- groups = []
353
+ def process(self, output) -> list[str]:
354
+ groups: list[str] = []
331
355
 
332
356
  for line in output:
333
357
  if ":" in line:
@@ -336,7 +360,17 @@ class Groups(FactBase):
336
360
  return groups
337
361
 
338
362
 
339
- class Crontab(FactBase):
363
+ class CrontabDict(TypedDict):
364
+ minute: NotRequired[Union[int, str]]
365
+ hour: NotRequired[Union[int, str]]
366
+ month: NotRequired[Union[int, str]]
367
+ day_of_month: NotRequired[Union[int, str]]
368
+ day_of_week: NotRequired[Union[int, str]]
369
+ comments: Optional[list[str]]
370
+ special_time: NotRequired[str]
371
+
372
+
373
+ class Crontab(FactBase[Dict[str, CrontabDict]]):
340
374
  """
341
375
  Returns a dictionary of cron command -> execution time.
342
376
 
@@ -366,9 +400,8 @@ class Crontab(FactBase):
366
400
  return "crontab -l -u {0} || true".format(user)
367
401
  return "crontab -l || true"
368
402
 
369
- @staticmethod
370
- def process(output):
371
- crons = {}
403
+ def process(self, output):
404
+ crons: dict[str, CrontabDict] = {}
372
405
  current_comments = []
373
406
 
374
407
  for line in output:
@@ -478,7 +511,14 @@ class Users(FactBase):
478
511
  return users
479
512
 
480
513
 
481
- class LinuxDistribution(FactBase):
514
+ class LinuxDistributionDict(TypedDict):
515
+ name: Optional[str]
516
+ major: Optional[int]
517
+ minor: Optional[int]
518
+ release_meta: Dict
519
+
520
+
521
+ class LinuxDistribution(FactBase[LinuxDistributionDict]):
482
522
  """
483
523
  Returns a dict of the Linux distribution version. Ubuntu, Debian, CentOS,
484
524
  Fedora & Gentoo currently. Also contains any key/value items located in
@@ -516,7 +556,7 @@ class LinuxDistribution(FactBase):
516
556
  }
517
557
 
518
558
  @staticmethod
519
- def default():
559
+ def default() -> LinuxDistributionDict:
520
560
  return {
521
561
  "name": None,
522
562
  "major": None,
@@ -524,7 +564,7 @@ class LinuxDistribution(FactBase):
524
564
  "release_meta": {},
525
565
  }
526
566
 
527
- def process(self, output):
567
+ def process(self, output) -> LinuxDistributionDict:
528
568
  parts = {}
529
569
  for part in "\n".join(output).strip().split("---"):
530
570
  if not part.strip():
@@ -578,7 +618,7 @@ class LinuxDistribution(FactBase):
578
618
  return release_info
579
619
 
580
620
 
581
- class LinuxName(ShortFactBase):
621
+ class LinuxName(ShortFactBase[str]):
582
622
  """
583
623
  Returns the name of the Linux distribution. Shortcut for
584
624
  ``host.get_fact(LinuxDistribution)['name']``.
@@ -591,7 +631,11 @@ class LinuxName(ShortFactBase):
591
631
  return data["name"]
592
632
 
593
633
 
594
- class Selinux(FactBase):
634
+ class SelinuxDict(TypedDict):
635
+ mode: Optional[str]
636
+
637
+
638
+ class Selinux(FactBase[SelinuxDict]):
595
639
  """
596
640
  Discovers the SELinux related facts on the target host.
597
641
 
@@ -606,12 +650,12 @@ class Selinux(FactBase):
606
650
  requires_command = "sestatus"
607
651
 
608
652
  @staticmethod
609
- def default():
653
+ def default() -> SelinuxDict:
610
654
  return {
611
655
  "mode": None,
612
656
  }
613
657
 
614
- def process(self, output):
658
+ def process(self, output) -> SelinuxDict:
615
659
  selinux_info = self.default()
616
660
 
617
661
  match = re.match(r"^SELinux status:\s+(\S+)", "\n".join(output))
@@ -624,7 +668,7 @@ class Selinux(FactBase):
624
668
  return selinux_info
625
669
 
626
670
 
627
- class LinuxGui(FactBase):
671
+ class LinuxGui(FactBase[List[str]]):
628
672
  """
629
673
  Returns a list of available Linux GUIs.
630
674
  """
@@ -640,7 +684,7 @@ class LinuxGui(FactBase):
640
684
  "/usr/bin/xfce4-session": "XFCE 4",
641
685
  }
642
686
 
643
- def process(self, output):
687
+ def process(self, output) -> list[str]:
644
688
  gui_names = []
645
689
 
646
690
  for line in output:
@@ -651,7 +695,7 @@ class LinuxGui(FactBase):
651
695
  return gui_names
652
696
 
653
697
 
654
- class HasGui(ShortFactBase):
698
+ class HasGui(ShortFactBase[bool]):
655
699
  """
656
700
  Returns a boolean indicating the remote side has GUI capabilities. Linux only.
657
701
  """
@@ -659,11 +703,11 @@ class HasGui(ShortFactBase):
659
703
  fact = LinuxGui
660
704
 
661
705
  @staticmethod
662
- def process_data(data):
706
+ def process_data(data) -> bool:
663
707
  return len(data) > 0
664
708
 
665
709
 
666
- class Locales(FactBase):
710
+ class Locales(FactBase[List[str]]):
667
711
  """
668
712
  Returns installed locales on the target host.
669
713
 
@@ -676,7 +720,7 @@ class Locales(FactBase):
676
720
  requires_command = "locale"
677
721
  default = list
678
722
 
679
- def process(self, output):
723
+ def process(self, output) -> list[str]:
680
724
  # replace utf8 with UTF-8 to match names in /etc/locale.gen
681
725
  # return a list of enabled locales
682
726
  return [line.replace("utf8", "UTF-8") for line in output]
pyinfra/facts/snap.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