pyinfra 3.0b1__tar.gz → 3.0b2__tar.gz

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 (180) hide show
  1. {pyinfra-3.0b1 → pyinfra-3.0b2}/CHANGELOG.md +1 -1
  2. {pyinfra-3.0b1/pyinfra.egg-info → pyinfra-3.0b2}/PKG-INFO +66 -11
  3. {pyinfra-3.0b1 → pyinfra-3.0b2}/README.md +6 -8
  4. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/api/arguments.py +9 -3
  5. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/api/arguments_typed.py +8 -5
  6. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/api/command.py +5 -3
  7. pyinfra-3.0b2/pyinfra/api/config.py +231 -0
  8. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/api/connectors.py +5 -2
  9. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/api/facts.py +33 -32
  10. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/api/host.py +5 -5
  11. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/api/inventory.py +4 -0
  12. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/api/operation.py +22 -14
  13. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/api/util.py +24 -16
  14. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/connectors/base.py +3 -6
  15. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/connectors/docker.py +2 -9
  16. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/connectors/local.py +2 -2
  17. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/connectors/ssh.py +2 -2
  18. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/connectors/util.py +6 -7
  19. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/connectors/vagrant.py +5 -5
  20. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/context.py +1 -0
  21. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/facts/apk.py +2 -0
  22. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/facts/apt.py +2 -0
  23. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/facts/brew.py +2 -0
  24. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/facts/bsdinit.py +2 -0
  25. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/facts/cargo.py +2 -0
  26. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/facts/choco.py +2 -0
  27. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/facts/deb.py +7 -2
  28. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/facts/dnf.py +2 -0
  29. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/facts/docker.py +2 -0
  30. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/facts/files.py +2 -0
  31. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/facts/gem.py +2 -0
  32. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/facts/gpg.py +2 -0
  33. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/facts/hardware.py +30 -22
  34. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/facts/launchd.py +2 -0
  35. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/facts/lxd.py +2 -0
  36. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/facts/mysql.py +12 -6
  37. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/facts/npm.py +1 -0
  38. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/facts/openrc.py +2 -0
  39. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/facts/pacman.py +6 -2
  40. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/facts/pip.py +2 -0
  41. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/facts/pkg.py +2 -0
  42. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/facts/pkgin.py +2 -0
  43. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/facts/postgres.py +6 -6
  44. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/facts/postgresql.py +2 -0
  45. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/facts/rpm.py +12 -9
  46. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/facts/server.py +10 -13
  47. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/facts/snap.py +2 -0
  48. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/facts/systemd.py +2 -0
  49. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/facts/upstart.py +2 -0
  50. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/facts/util/packaging.py +3 -2
  51. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/facts/vzctl.py +2 -0
  52. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/facts/xbps.py +2 -0
  53. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/facts/yum.py +2 -0
  54. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/facts/zypper.py +2 -0
  55. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/operations/apk.py +3 -1
  56. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/operations/apt.py +16 -18
  57. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/operations/brew.py +10 -8
  58. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/operations/bsdinit.py +5 -3
  59. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/operations/cargo.py +3 -1
  60. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/operations/choco.py +3 -1
  61. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/operations/dnf.py +15 -19
  62. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/operations/files.py +81 -66
  63. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/operations/gem.py +3 -1
  64. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/operations/git.py +18 -16
  65. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/operations/iptables.py +27 -25
  66. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/operations/launchd.py +5 -6
  67. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/operations/lxd.py +7 -4
  68. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/operations/mysql.py +57 -53
  69. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/operations/npm.py +8 -1
  70. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/operations/openrc.py +5 -3
  71. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/operations/pacman.py +4 -5
  72. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/operations/pip.py +11 -9
  73. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/operations/pkg.py +3 -1
  74. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/operations/pkgin.py +3 -1
  75. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/operations/postgres.py +39 -37
  76. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/operations/postgresql.py +2 -0
  77. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/operations/puppet.py +3 -1
  78. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/operations/python.py +7 -3
  79. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/operations/selinux.py +42 -16
  80. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/operations/server.py +48 -43
  81. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/operations/snap.py +3 -1
  82. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/operations/ssh.py +12 -10
  83. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/operations/systemd.py +8 -6
  84. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/operations/sysvinit.py +6 -4
  85. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/operations/upstart.py +5 -3
  86. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/operations/util/files.py +24 -16
  87. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/operations/util/packaging.py +53 -37
  88. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/operations/util/service.py +18 -13
  89. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/operations/vzctl.py +12 -10
  90. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/operations/xbps.py +3 -1
  91. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/operations/yum.py +14 -18
  92. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/operations/zypper.py +8 -9
  93. pyinfra-3.0b2/pyinfra/version.py +9 -0
  94. {pyinfra-3.0b1 → pyinfra-3.0b2/pyinfra.egg-info}/PKG-INFO +66 -11
  95. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra.egg-info/SOURCES.txt +0 -3
  96. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra.egg-info/requires.txt +20 -16
  97. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra_cli/exceptions.py +0 -5
  98. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra_cli/inventory.py +38 -19
  99. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra_cli/prints.py +15 -11
  100. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra_cli/util.py +3 -1
  101. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyproject.toml +1 -1
  102. {pyinfra-3.0b1 → pyinfra-3.0b2}/setup.cfg +1 -1
  103. {pyinfra-3.0b1 → pyinfra-3.0b2}/setup.py +14 -9
  104. {pyinfra-3.0b1 → pyinfra-3.0b2}/tests/test_api/test_api_operations.py +1 -1
  105. {pyinfra-3.0b1 → pyinfra-3.0b2}/tests/test_connectors/test_ssh.py +66 -13
  106. {pyinfra-3.0b1 → pyinfra-3.0b2}/tests/test_connectors/test_vagrant.py +3 -3
  107. pyinfra-3.0b1/pyinfra/api/config.py +0 -129
  108. pyinfra-3.0b1/pyinfra/version.py +0 -6
  109. pyinfra-3.0b1/tests/__init__.py +0 -12
  110. pyinfra-3.0b1/tests/paramiko_util.py +0 -103
  111. pyinfra-3.0b1/tests/util.py +0 -407
  112. {pyinfra-3.0b1 → pyinfra-3.0b2}/LICENSE.md +0 -0
  113. {pyinfra-3.0b1 → pyinfra-3.0b2}/MANIFEST.in +0 -0
  114. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/__init__.py +0 -0
  115. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/__main__.py +0 -0
  116. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/api/__init__.py +0 -0
  117. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/api/connect.py +0 -0
  118. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/api/deploy.py +0 -0
  119. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/api/exceptions.py +0 -0
  120. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/api/operations.py +0 -0
  121. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/api/state.py +0 -0
  122. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/connectors/__init__.py +0 -0
  123. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/connectors/chroot.py +0 -0
  124. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/connectors/dockerssh.py +0 -0
  125. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/connectors/ssh_util.py +0 -0
  126. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/connectors/sshuserclient/__init__.py +0 -0
  127. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/connectors/sshuserclient/client.py +0 -0
  128. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/connectors/sshuserclient/config.py +0 -0
  129. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/connectors/terraform.py +0 -0
  130. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/facts/__init__.py +0 -0
  131. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/facts/git.py +0 -0
  132. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/facts/iptables.py +0 -0
  133. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/facts/selinux.py +0 -0
  134. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/facts/sysvinit.py +0 -0
  135. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/facts/util/__init__.py +0 -0
  136. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/facts/util/databases.py +0 -0
  137. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/facts/util/win_files.py +0 -0
  138. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/local.py +0 -0
  139. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/operations/__init__.py +0 -0
  140. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/operations/util/__init__.py +0 -0
  141. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/progress.py +0 -0
  142. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra/py.typed +0 -0
  143. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra.egg-info/dependency_links.txt +0 -0
  144. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra.egg-info/entry_points.txt +0 -0
  145. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra.egg-info/top_level.txt +0 -0
  146. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra_cli/__init__.py +0 -0
  147. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra_cli/__main__.py +0 -0
  148. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra_cli/commands.py +0 -0
  149. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra_cli/log.py +0 -0
  150. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra_cli/main.py +0 -0
  151. {pyinfra-3.0b1 → pyinfra-3.0b2}/pyinfra_cli/virtualenv.py +0 -0
  152. {pyinfra-3.0b1 → pyinfra-3.0b2}/tests/test_api/__init__.py +0 -0
  153. {pyinfra-3.0b1 → pyinfra-3.0b2}/tests/test_api/test_api.py +0 -0
  154. {pyinfra-3.0b1 → pyinfra-3.0b2}/tests/test_api/test_api_arguments.py +0 -0
  155. {pyinfra-3.0b1 → pyinfra-3.0b2}/tests/test_api/test_api_command.py +0 -0
  156. {pyinfra-3.0b1 → pyinfra-3.0b2}/tests/test_api/test_api_config.py +0 -0
  157. {pyinfra-3.0b1 → pyinfra-3.0b2}/tests/test_api/test_api_deploys.py +0 -0
  158. {pyinfra-3.0b1 → pyinfra-3.0b2}/tests/test_api/test_api_facts.py +0 -0
  159. {pyinfra-3.0b1 → pyinfra-3.0b2}/tests/test_api/test_api_host.py +0 -0
  160. {pyinfra-3.0b1 → pyinfra-3.0b2}/tests/test_api/test_api_inventory.py +0 -0
  161. {pyinfra-3.0b1 → pyinfra-3.0b2}/tests/test_api/test_api_util.py +0 -0
  162. {pyinfra-3.0b1 → pyinfra-3.0b2}/tests/test_cli/__init__.py +0 -0
  163. {pyinfra-3.0b1 → pyinfra-3.0b2}/tests/test_cli/test_cli.py +0 -0
  164. {pyinfra-3.0b1 → pyinfra-3.0b2}/tests/test_cli/test_cli_deploy.py +0 -0
  165. {pyinfra-3.0b1 → pyinfra-3.0b2}/tests/test_cli/test_cli_exceptions.py +0 -0
  166. {pyinfra-3.0b1 → pyinfra-3.0b2}/tests/test_cli/test_cli_util.py +0 -0
  167. {pyinfra-3.0b1 → pyinfra-3.0b2}/tests/test_cli/test_context_objects.py +0 -0
  168. {pyinfra-3.0b1 → pyinfra-3.0b2}/tests/test_cli/util.py +0 -0
  169. {pyinfra-3.0b1 → pyinfra-3.0b2}/tests/test_connectors/__init__.py +0 -0
  170. {pyinfra-3.0b1 → pyinfra-3.0b2}/tests/test_connectors/test_chroot.py +0 -0
  171. {pyinfra-3.0b1 → pyinfra-3.0b2}/tests/test_connectors/test_docker.py +0 -0
  172. {pyinfra-3.0b1 → pyinfra-3.0b2}/tests/test_connectors/test_dockerssh.py +0 -0
  173. {pyinfra-3.0b1 → pyinfra-3.0b2}/tests/test_connectors/test_local.py +0 -0
  174. {pyinfra-3.0b1 → pyinfra-3.0b2}/tests/test_connectors/test_sshuserclient.py +0 -0
  175. {pyinfra-3.0b1 → pyinfra-3.0b2}/tests/test_connectors/test_terraform.py +0 -0
  176. {pyinfra-3.0b1 → pyinfra-3.0b2}/tests/test_connectors/test_util.py +0 -0
  177. {pyinfra-3.0b1 → pyinfra-3.0b2}/tests/test_facts.py +0 -0
  178. {pyinfra-3.0b1 → pyinfra-3.0b2}/tests/test_global_arguments.py +0 -0
  179. {pyinfra-3.0b1 → pyinfra-3.0b2}/tests/test_operations.py +0 -0
  180. {pyinfra-3.0b1 → pyinfra-3.0b2}/tests/test_operations_utils.py +0 -0
@@ -1,4 +1,4 @@
1
- # v3.0.beta1
1
+ # v3.0.beta2
2
2
 
3
3
  Welcome to pyinfra v3! This version is the biggest overhaul of pyinfra since it was created back in 2015. Most v2 deployment code should be automatically compatible, but as always be aware. Major changes:
4
4
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pyinfra
3
- Version: 3.0b1
3
+ Version: 3.0b2
4
4
  Summary: pyinfra automates/provisions/manages/deploys infrastructure.
5
5
  Home-page: https://pyinfra.com
6
6
  Author: Nick / Fizzadar
@@ -16,18 +16,75 @@ Classifier: Intended Audience :: Information Technology
16
16
  Classifier: License :: OSI Approved :: MIT License
17
17
  Classifier: Operating System :: OS Independent
18
18
  Classifier: Programming Language :: Python :: 3
19
- Classifier: Programming Language :: Python :: 3.7
20
19
  Classifier: Programming Language :: Python :: 3.8
21
20
  Classifier: Programming Language :: Python :: 3.9
22
21
  Classifier: Programming Language :: Python :: 3.10
22
+ Classifier: Programming Language :: Python :: 3.11
23
+ Classifier: Programming Language :: Python :: 3.12
23
24
  Classifier: Topic :: System :: Systems Administration
24
25
  Classifier: Topic :: System :: Installation/Setup
25
26
  Classifier: Topic :: Utilities
27
+ Requires-Python: >=3.8
26
28
  Description-Content-Type: text/markdown
29
+ License-File: LICENSE.md
30
+ Requires-Dist: gevent>=1.5
31
+ Requires-Dist: paramiko<4,>=2.7
32
+ Requires-Dist: click>2
33
+ Requires-Dist: jinja2<4,>2
34
+ Requires-Dist: python-dateutil<3,>2
35
+ Requires-Dist: setuptools
36
+ Requires-Dist: configparser
37
+ Requires-Dist: pywinrm
38
+ Requires-Dist: typeguard
39
+ Requires-Dist: distro<2,>=1.6
40
+ Requires-Dist: packaging>=16.1
41
+ Requires-Dist: graphlib_backport; python_version < "3.9"
42
+ Requires-Dist: typing-extensions; python_version < "3.11"
43
+ Requires-Dist: importlib_metadata>=3.6; python_version < "3.10"
27
44
  Provides-Extra: test
45
+ Requires-Dist: pytest==8.2.1; extra == "test"
46
+ Requires-Dist: coverage==7.5.1; extra == "test"
47
+ Requires-Dist: pytest-cov==5.0.0; extra == "test"
48
+ Requires-Dist: black==24.4.2; extra == "test"
49
+ Requires-Dist: isort==5.13.2; extra == "test"
50
+ Requires-Dist: flake8==7.0.0; extra == "test"
51
+ Requires-Dist: flake8-black==0.3.6; extra == "test"
52
+ Requires-Dist: flake8-isort==6.1.1; extra == "test"
53
+ Requires-Dist: mypy; extra == "test"
54
+ Requires-Dist: types-cryptography; extra == "test"
55
+ Requires-Dist: types-paramiko; extra == "test"
56
+ Requires-Dist: types-python-dateutil; extra == "test"
57
+ Requires-Dist: types-PyYAML; extra == "test"
58
+ Requires-Dist: types-setuptools; extra == "test"
28
59
  Provides-Extra: docs
60
+ Requires-Dist: pyinfra-guzzle_sphinx_theme==0.15; extra == "docs"
61
+ Requires-Dist: myst-parser==2.0.0; extra == "docs"
62
+ Requires-Dist: sphinx==6.2.1; extra == "docs"
29
63
  Provides-Extra: dev
30
- License-File: LICENSE.md
64
+ Requires-Dist: pytest==8.2.1; extra == "dev"
65
+ Requires-Dist: coverage==7.5.1; extra == "dev"
66
+ Requires-Dist: pytest-cov==5.0.0; extra == "dev"
67
+ Requires-Dist: black==24.4.2; extra == "dev"
68
+ Requires-Dist: isort==5.13.2; extra == "dev"
69
+ Requires-Dist: flake8==7.0.0; extra == "dev"
70
+ Requires-Dist: flake8-black==0.3.6; extra == "dev"
71
+ Requires-Dist: flake8-isort==6.1.1; extra == "dev"
72
+ Requires-Dist: mypy; extra == "dev"
73
+ Requires-Dist: types-cryptography; extra == "dev"
74
+ Requires-Dist: types-paramiko; extra == "dev"
75
+ Requires-Dist: types-python-dateutil; extra == "dev"
76
+ Requires-Dist: types-PyYAML; extra == "dev"
77
+ Requires-Dist: types-setuptools; extra == "dev"
78
+ Requires-Dist: pyinfra-guzzle_sphinx_theme==0.15; extra == "dev"
79
+ Requires-Dist: myst-parser==2.0.0; extra == "dev"
80
+ Requires-Dist: sphinx==6.2.1; extra == "dev"
81
+ Requires-Dist: wheel; extra == "dev"
82
+ Requires-Dist: twine; extra == "dev"
83
+ Requires-Dist: ipython; extra == "dev"
84
+ Requires-Dist: ipdb; extra == "dev"
85
+ Requires-Dist: ipdbplugin; extra == "dev"
86
+ Requires-Dist: flake8-spellcheck==0.12.1; extra == "dev"
87
+ Requires-Dist: redbaron; extra == "dev"
31
88
 
32
89
  <p align="center">
33
90
  <a href="https://pyinfra.com">
@@ -45,19 +102,17 @@ License-File: LICENSE.md
45
102
 
46
103
  ---
47
104
 
48
- <p>
49
- <a href="https://docs.pyinfra.com"><strong>Documentation</strong></a> &rArr;
105
+ <h3>
50
106
  <a href="https://docs.pyinfra.com/page/getting-started.html"><strong>Getting Started</strong></a> &bull;
51
- <a href="https://docs.pyinfra.com/page/examples.html"><strong>Examples</strong></a> &bull;
107
+ <a href="https://github.com/pyinfra-dev/pyinfra-examples"><strong>Examples Repo</strong></a> &bull;
108
+ <a href="https://matrix.to/#/#pyinfra:matrix.org"><strong>Chat on Matrix</strong></a>
109
+ </h3>
110
+ <p>
111
+ <a href="https://docs.pyinfra.com"><strong>Documentation</strong></a> &bull;
52
112
  <a href="https://docs.pyinfra.com/page/support.html"><strong>Help & Support</strong></a> &bull;
53
113
  <a href="https://docs.pyinfra.com/page/contributing.html"><strong>Contributing</strong></a>
54
114
  </p>
55
115
 
56
- <p>
57
- Chat &rArr;
58
- <a href="https://matrix.to/#/#pyinfra:matrix.org"><strong><code>#pyinfra</code> on Matrix</strong></a>
59
- </p>
60
-
61
116
  ---
62
117
 
63
118
  Why pyinfra? Design features include:
@@ -14,19 +14,17 @@
14
14
 
15
15
  ---
16
16
 
17
- <p>
18
- <a href="https://docs.pyinfra.com"><strong>Documentation</strong></a> &rArr;
17
+ <h3>
19
18
  <a href="https://docs.pyinfra.com/page/getting-started.html"><strong>Getting Started</strong></a> &bull;
20
- <a href="https://docs.pyinfra.com/page/examples.html"><strong>Examples</strong></a> &bull;
19
+ <a href="https://github.com/pyinfra-dev/pyinfra-examples"><strong>Examples Repo</strong></a> &bull;
20
+ <a href="https://matrix.to/#/#pyinfra:matrix.org"><strong>Chat on Matrix</strong></a>
21
+ </h3>
22
+ <p>
23
+ <a href="https://docs.pyinfra.com"><strong>Documentation</strong></a> &bull;
21
24
  <a href="https://docs.pyinfra.com/page/support.html"><strong>Help & Support</strong></a> &bull;
22
25
  <a href="https://docs.pyinfra.com/page/contributing.html"><strong>Contributing</strong></a>
23
26
  </p>
24
27
 
25
- <p>
26
- Chat &rArr;
27
- <a href="https://matrix.to/#/#pyinfra:matrix.org"><strong><code>#pyinfra</code> on Matrix</strong></a>
28
- </p>
29
-
30
28
  ---
31
29
 
32
30
  Why pyinfra? Design features include:
@@ -9,6 +9,7 @@ from typing import (
9
9
  List,
10
10
  Mapping,
11
11
  Optional,
12
+ Type,
12
13
  TypeVar,
13
14
  Union,
14
15
  cast,
@@ -170,7 +171,7 @@ class MetaArguments(TypedDict):
170
171
  name: str
171
172
  _ignore_errors: bool
172
173
  _continue_on_error: bool
173
- _if: List[Callable[[], bool]]
174
+ _if: Union[List[Callable[[], bool]], Callable[[], bool], None]
174
175
 
175
176
 
176
177
  meta_argument_meta: dict[str, ArgumentMeta] = {
@@ -191,7 +192,7 @@ meta_argument_meta: dict[str, ArgumentMeta] = {
191
192
  default=lambda _: False,
192
193
  ),
193
194
  "_if": ArgumentMeta(
194
- "Only run this operation if these functions returns True",
195
+ "Only run this operation if these functions return True",
195
196
  default=lambda _: [],
196
197
  ),
197
198
  }
@@ -228,6 +229,11 @@ class AllArguments(ConnectorArguments, MetaArguments, ExecutionArguments):
228
229
  pass
229
230
 
230
231
 
232
+ def all_global_arguments() -> List[tuple[str, Type]]:
233
+ """Return all global arguments and their types."""
234
+ return list(get_type_hints(AllArguments).items())
235
+
236
+
231
237
  all_argument_meta: dict[str, ArgumentMeta] = {
232
238
  **auth_argument_meta,
233
239
  **shell_argument_meta,
@@ -305,7 +311,7 @@ def pop_global_arguments(
305
311
  arguments: dict[str, Any] = {}
306
312
  found_keys: list[str] = []
307
313
 
308
- for key, type_ in get_type_hints(AllArguments).items():
314
+ for key, type_ in all_global_arguments():
309
315
  if keys_to_check and key not in keys_to_check:
310
316
  continue
311
317
 
@@ -32,6 +32,11 @@ class PyinfraOperation(Generic[P], Protocol):
32
32
  def __call__(
33
33
  self,
34
34
  #
35
+ # op args
36
+ # needs to be first
37
+ #
38
+ *args: P.args,
39
+ #
35
40
  # ConnectorArguments
36
41
  #
37
42
  # Auth
@@ -61,7 +66,7 @@ class PyinfraOperation(Generic[P], Protocol):
61
66
  name: Optional[str] = None,
62
67
  _ignore_errors: bool = False,
63
68
  _continue_on_error: bool = False,
64
- _if: Optional[List[Callable[[], bool]]] = None,
69
+ _if: Union[List[Callable[[], bool]], Callable[[], bool], None] = None,
65
70
  #
66
71
  # ExecutionArguments
67
72
  #
@@ -69,9 +74,7 @@ class PyinfraOperation(Generic[P], Protocol):
69
74
  _run_once: bool = False,
70
75
  _serial: bool = False,
71
76
  #
72
- # The op itself
77
+ # op kwargs
73
78
  #
74
- *args: P.args,
75
79
  **kwargs: P.kwargs,
76
- ) -> "OperationMeta":
77
- ...
80
+ ) -> "OperationMeta": ...
@@ -1,7 +1,9 @@
1
+ from __future__ import annotations
2
+
1
3
  import shlex
2
4
  from inspect import getfullargspec
3
5
  from string import Formatter
4
- from typing import TYPE_CHECKING, Callable, Union
6
+ from typing import IO, TYPE_CHECKING, Callable, Union
5
7
 
6
8
  import gevent
7
9
  from typing_extensions import Unpack
@@ -143,7 +145,7 @@ class StringCommand(PyinfraCommand):
143
145
  class FileUploadCommand(PyinfraCommand):
144
146
  def __init__(
145
147
  self,
146
- src: str,
148
+ src: str | IO,
147
149
  dest: str,
148
150
  remote_temp_filename=None,
149
151
  **kwargs: Unpack[ConnectorArguments],
@@ -173,7 +175,7 @@ class FileDownloadCommand(PyinfraCommand):
173
175
  def __init__(
174
176
  self,
175
177
  src: str,
176
- dest: str,
178
+ dest: str | IO,
177
179
  remote_temp_filename=None,
178
180
  **kwargs: Unpack[ConnectorArguments],
179
181
  ):
@@ -0,0 +1,231 @@
1
+ try:
2
+ import importlib_metadata
3
+ except ImportError:
4
+ import importlib.metadata as importlib_metadata # type: ignore[no-redef]
5
+ from os import path
6
+ from typing import Iterable, Optional, Set
7
+
8
+ from packaging.markers import Marker
9
+ from packaging.requirements import Requirement
10
+ from packaging.specifiers import SpecifierSet
11
+ from packaging.version import Version
12
+
13
+ from pyinfra import __version__, state
14
+
15
+ from .exceptions import PyinfraError
16
+
17
+
18
+ class ConfigDefaults:
19
+ # % of hosts which have to fail for all operations to stop
20
+ FAIL_PERCENT: Optional[int] = None
21
+ # Seconds to timeout SSH connections
22
+ CONNECT_TIMEOUT: int = 10
23
+ # Temporary directory (on the remote side) to use for caching any files/downloads, the default
24
+ # None value first tries to load the hosts' temporary directory configured via "TMPDIR" env
25
+ # variable, falling back to DEFAULT_TEMP_DIR if not set.
26
+ TEMP_DIR: Optional[str] = None
27
+ DEFAULT_TEMP_DIR: str = "/tmp"
28
+ # Gevent pool size (defaults to #of target hosts)
29
+ PARALLEL: int = 0
30
+ # Specify the required pyinfra version (using PEP 440 setuptools specifier)
31
+ REQUIRE_PYINFRA_VERSION: Optional[str] = None
32
+ # Specify any required packages (either using PEP 440 or a requirements file)
33
+ # Note: this can also include pyinfra potentially replacing REQUIRE_PYINFRA_VERSION
34
+ REQUIRE_PACKAGES: Optional[str] = None
35
+ # All these can be overridden inside individual operation calls:
36
+ # Switch to this user (from ssh_user) using su before executing operations
37
+ SU_USER: Optional[str] = None
38
+ USE_SU_LOGIN: bool = False
39
+ SU_SHELL: bool = False
40
+ PRESERVE_SU_ENV: bool = False
41
+ # Use sudo and optional user
42
+ SUDO: bool = False
43
+ SUDO_USER: Optional[str] = None
44
+ PRESERVE_SUDO_ENV: bool = False
45
+ USE_SUDO_LOGIN: bool = False
46
+ SUDO_PASSWORD: Optional[str] = None
47
+ # Use doas and optional user
48
+ DOAS: bool = False
49
+ DOAS_USER: Optional[str] = None
50
+ # Only show errors but don't count as failure
51
+ IGNORE_ERRORS: bool = False
52
+ # Shell to use to execute commands
53
+ SHELL: str = "sh"
54
+
55
+
56
+ config_defaults = {key: value for key, value in ConfigDefaults.__dict__.items() if key.isupper()}
57
+
58
+
59
+ def check_pyinfra_version(version: str):
60
+ if not version:
61
+ return
62
+ running_version = Version(__version__)
63
+ required_versions = SpecifierSet(version)
64
+
65
+ if running_version not in required_versions:
66
+ raise PyinfraError(
67
+ f"pyinfra version requirement not met (requires {version}, running {__version__})"
68
+ )
69
+
70
+
71
+ def _check_requirements(requirements: Iterable[str]) -> Set[Requirement]:
72
+ """
73
+ Check whether each of the given requirements and all their dependencies are
74
+ installed.
75
+
76
+ Or more precisely, this checks that each of the given *requirements* is
77
+ satisfied by some installed *distribution package*, and so on recursively
78
+ for each of the dependencies of those distribution packages. The terminology
79
+ here is as follows:
80
+
81
+ * A *distribution package* is essentially a thing that can be installed with
82
+ ``pip``, from an sdist or wheel or Git repo or so on.
83
+ * A *requirement* is the expectation that a distribution package satisfying
84
+ some constraint is installed.
85
+ * A *dependency* is a requirement specified by a distribution package (as
86
+ opposed to the requirements passed in to this function).
87
+
88
+ So what this function does is start from the given requirements, for each
89
+ one check that it is satisfied by some installed distribution package, and
90
+ if so recursively perform the same check on all the dependencies of that
91
+ distribution package. In short, it's traversing the graph of package
92
+ requirements. It stops whenever it finds a requirement that is not satisfied
93
+ (i.e. a required package that is not installed), or when it runs out of
94
+ requirements to check.
95
+
96
+ .. note::
97
+ This is basically equivalent to ``pkg_resources.require()`` except that
98
+ when ``require()`` succeeds, it will return the list of distribution
99
+ packages that satisfy the given requirements and their dependencies, and
100
+ when it fails, it will raise an exception. This function just returns
101
+ the requirements which were not satisfied instead.
102
+
103
+ :param requirements: The requirements to check for in the set of installed
104
+ packages (along with their dependencies).
105
+ :return: The set of requirements that were not satisfied, which will be
106
+ an empty set if all requirements (recursively) were satisfied.
107
+ """
108
+
109
+ # Based on pkg_resources.require() from setuptools. The implementation of
110
+ # hbutils.system.check_reqs() from the hbutils package was also helpful in
111
+ # clarifying what this is supposed to do.
112
+
113
+ reqs_to_check: Set[Requirement] = set(Requirement(r) for r in requirements)
114
+ reqs_satisfied: Set[Requirement] = set()
115
+ reqs_not_satisfied: Set[Requirement] = set()
116
+
117
+ while reqs_to_check:
118
+ req = reqs_to_check.pop()
119
+ assert req not in reqs_satisfied and req not in reqs_not_satisfied
120
+
121
+ # Check for an installed distribution package with the right name and version
122
+ try:
123
+ dist = importlib_metadata.distribution(req.name)
124
+ except importlib_metadata.PackageNotFoundError:
125
+ # No installed package with the right name
126
+ # This would raise a DistributionNotFound error from pkg_resources.require()
127
+ reqs_not_satisfied.add(req)
128
+ continue
129
+
130
+ if dist.version not in req.specifier:
131
+ # There is a distribution with the right name but wrong version
132
+ # This would raise a VersionConflict error from pkg_resources.require()
133
+ reqs_not_satisfied.add(req)
134
+ continue
135
+
136
+ reqs_satisfied.add(req)
137
+
138
+ # If the distribution package has dependencies of its own, go through
139
+ # those dependencies and for each one add it to the set to be checked if
140
+ # - it's unconditional (no marker)
141
+ # - or it's conditional and the condition is satisfied (the marker
142
+ # evaluates to true) in the current environment
143
+ # Markers can check things like the Python version and system version
144
+ # etc., and/or they can check which extras of the distribution package
145
+ # were required. To facilitate checking extras we have to pass the extra
146
+ # in the environment when calling Marker.evaluate().
147
+ if dist.requires:
148
+ if req.extras:
149
+ extras_envs = [{"extra": extra} for extra in req.extras]
150
+
151
+ def evaluate_marker(marker: Marker) -> bool:
152
+ return any(map(marker.evaluate, extras_envs))
153
+
154
+ else:
155
+
156
+ def evaluate_marker(marker: Marker) -> bool:
157
+ return marker.evaluate()
158
+
159
+ for dist_req_str in dist.requires:
160
+ dist_req = Requirement(dist_req_str)
161
+ if dist_req in reqs_satisfied or dist_req in reqs_not_satisfied:
162
+ continue
163
+ if (not dist_req.marker) or evaluate_marker(dist_req.marker):
164
+ reqs_to_check.add(dist_req)
165
+
166
+ return reqs_not_satisfied
167
+
168
+
169
+ def check_require_packages(requirements_config):
170
+ if not requirements_config:
171
+ return
172
+
173
+ if isinstance(requirements_config, (list, tuple)):
174
+ requirements = requirements_config
175
+ else:
176
+ with open(path.join(state.cwd or "", requirements_config), encoding="utf-8") as f:
177
+ requirements = [line.split("#egg=")[-1] for line in f.read().splitlines()]
178
+
179
+ requirements_not_met = _check_requirements(requirements)
180
+ if requirements_not_met:
181
+ raise PyinfraError(
182
+ "Deploy requirements ({0}) not met: missing {1}".format(
183
+ requirements_config, ", ".join(str(r) for r in requirements_not_met)
184
+ )
185
+ )
186
+
187
+
188
+ config_checkers = {
189
+ "REQUIRE_PYINFRA_VERSION": check_pyinfra_version,
190
+ "REQUIRE_PACKAGES": check_require_packages,
191
+ }
192
+
193
+
194
+ class Config(ConfigDefaults):
195
+ """
196
+ The default/base configuration options for a pyinfra deploy.
197
+ """
198
+
199
+ def __init__(self, **kwargs):
200
+ # Always apply some env
201
+ env = kwargs.pop("ENV", {})
202
+ self.ENV = env
203
+
204
+ config = config_defaults.copy()
205
+ config.update(kwargs)
206
+
207
+ for key, value in config.items():
208
+ setattr(self, key, value)
209
+
210
+ def __setattr__(self, key, value):
211
+ super().__setattr__(key, value)
212
+
213
+ checker = config_checkers.get(key)
214
+ if checker:
215
+ checker(value)
216
+
217
+ def get_current_state(self):
218
+ return [(key, getattr(self, key)) for key in config_defaults.keys()]
219
+
220
+ def set_current_state(self, config_state):
221
+ for key, value in config_state:
222
+ setattr(self, key, value)
223
+
224
+ def lock_current_state(self):
225
+ self._locked_config = self.get_current_state()
226
+
227
+ def reset_locked_state(self):
228
+ self.set_current_state(self._locked_config)
229
+
230
+ def copy(self) -> "Config":
231
+ return Config(**dict(self.get_current_state()))
@@ -1,4 +1,7 @@
1
- import pkg_resources
1
+ try:
2
+ from importlib_metadata import entry_points
3
+ except ImportError:
4
+ from importlib.metadata import entry_points # type: ignore[assignment]
2
5
 
3
6
 
4
7
  def _load_connector(entrypoint):
@@ -8,7 +11,7 @@ def _load_connector(entrypoint):
8
11
  def get_all_connectors():
9
12
  return {
10
13
  entrypoint.name: _load_connector(entrypoint)
11
- for entrypoint in pkg_resources.iter_entry_points("pyinfra.connectors")
14
+ for entrypoint in entry_points(group="pyinfra.connectors")
12
15
  }
13
16
 
14
17
 
@@ -10,6 +10,7 @@ other host B while I operate on this host A).
10
10
 
11
11
  from __future__ import annotations
12
12
 
13
+ import inspect
13
14
  import re
14
15
  from inspect import getcallargs
15
16
  from socket import error as socket_error, timeout as timeout_error
@@ -32,7 +33,7 @@ from paramiko import SSHException
32
33
 
33
34
  from pyinfra import logger
34
35
  from pyinfra.api import StringCommand
35
- from pyinfra.api.arguments import pop_global_arguments
36
+ from pyinfra.api.arguments import all_global_arguments, pop_global_arguments
36
37
  from pyinfra.api.util import (
37
38
  get_kwargs_str,
38
39
  log_error_or_warning,
@@ -76,6 +77,17 @@ class FactBase(Generic[T]):
76
77
  module_name = cls.__module__.replace("pyinfra.facts.", "")
77
78
  cls.name = f"{module_name}.{cls.__name__}"
78
79
 
80
+ # Check that fact's `command` method does not inadvertently take a global
81
+ # argument, most commonly `name`.
82
+ if hasattr(cls, "command") and callable(cls.command):
83
+ command_args = set(inspect.signature(cls.command).parameters.keys())
84
+ global_args = set([name for name, _ in all_global_arguments()])
85
+ command_global_args = command_args & global_args
86
+
87
+ if len(command_global_args) > 0:
88
+ names = ", ".join(command_global_args)
89
+ raise TypeError(f"{cls.name}'s arguments {names} are reserved for global arguments")
90
+
79
91
  @staticmethod
80
92
  def default() -> T:
81
93
  """
@@ -146,34 +158,25 @@ def _handle_fact_kwargs(state, host, cls, args, kwargs):
146
158
  args = args or []
147
159
  kwargs = kwargs or {}
148
160
 
149
- # TODO: this is here to avoid popping stuff accidentally, this is horrible! Change the
150
- # pop function to return the clean kwargs to avoid the indirect mutation.
151
- kwargs = kwargs.copy()
161
+ # Start with a (shallow) copy of current operation kwargs if any
162
+ ctx_kwargs = (host.current_op_global_arguments or {}).copy()
163
+ # Update with the input kwargs (overrides)
164
+ ctx_kwargs.update(kwargs)
152
165
 
153
- # Get the defaults *and* overrides by popping from kwargs, executor kwargs passed
154
- # into get_fact override everything else (applied below).
155
- override_kwargs, override_kwarg_keys = pop_global_arguments(
156
- kwargs,
166
+ # Pop executor kwargs, pass remaining
167
+ global_kwargs, _ = pop_global_arguments(
168
+ ctx_kwargs,
157
169
  state=state,
158
170
  host=host,
159
- keys_to_check=CONNECTOR_ARGUMENT_KEYS,
160
- )
161
-
162
- executor_kwargs = _get_executor_kwargs(
163
- state,
164
- host,
165
- override_kwargs=override_kwargs, # type: ignore[arg-type]
166
- override_kwarg_keys=override_kwarg_keys,
167
171
  )
168
172
 
169
- fact_kwargs = {}
173
+ fact_kwargs = {key: value for key, value in kwargs.items() if key not in global_kwargs}
170
174
 
171
- if args or kwargs:
172
- assert not isinstance(cls.command, str)
175
+ if args or fact_kwargs:
173
176
  # Merges args & kwargs into a single kwargs dictionary
174
- fact_kwargs = getcallargs(cls().command, *args, **kwargs)
177
+ fact_kwargs = getcallargs(cls().command, *args, **fact_kwargs)
175
178
 
176
- return fact_kwargs, executor_kwargs
179
+ return fact_kwargs, global_kwargs
177
180
 
178
181
 
179
182
  def get_facts(state: "State", *args, **kwargs):
@@ -241,7 +244,7 @@ def _get_fact(
241
244
  fact = cls()
242
245
  name = fact.name
243
246
 
244
- fact_kwargs, executor_kwargs = _handle_fact_kwargs(state, host, cls, args, kwargs)
247
+ fact_kwargs, global_kwargs = _handle_fact_kwargs(state, host, cls, args, kwargs)
245
248
 
246
249
  kwargs_str = get_kwargs_str(fact_kwargs)
247
250
  logger.debug(
@@ -257,15 +260,9 @@ def _get_fact(
257
260
  raise_exceptions=True,
258
261
  )
259
262
 
260
- ignore_errors = (
261
- host.current_op_global_arguments["_ignore_errors"]
262
- if host.in_op and host.current_op_global_arguments
263
- else state.config.IGNORE_ERRORS
264
- )
265
-
266
263
  # Facts can override the shell (winrm powershell vs cmd support)
267
264
  if fact.shell_executable:
268
- executor_kwargs["_shell_executable"] = fact.shell_executable
265
+ global_kwargs["_shell_executable"] = fact.shell_executable
269
266
 
270
267
  command = _make_command(fact.command, fact_kwargs)
271
268
  requires_command = _make_command(fact.requires_command, fact_kwargs)
@@ -284,6 +281,10 @@ def _get_fact(
284
281
  status = False
285
282
  output = CommandOutput([])
286
283
 
284
+ executor_kwargs = {
285
+ key: value for key, value in global_kwargs.items() if key in CONNECTOR_ARGUMENT_KEYS
286
+ }
287
+
287
288
  try:
288
289
  status, output = host.run_shell_command(
289
290
  command,
@@ -295,7 +296,7 @@ def _get_fact(
295
296
  log_host_command_error(
296
297
  host,
297
298
  e,
298
- timeout=executor_kwargs["_timeout"],
299
+ timeout=global_kwargs["_timeout"],
299
300
  )
300
301
 
301
302
  stdout_lines, stderr_lines = output.stdout_lines, output.stderr_lines
@@ -334,12 +335,12 @@ def _get_fact(
334
335
 
335
336
  log_error_or_warning(
336
337
  host,
337
- ignore_errors,
338
+ global_kwargs["_ignore_errors"],
338
339
  description=("could not load fact: {0} {1}").format(name, get_kwargs_str(fact_kwargs)),
339
340
  )
340
341
 
341
342
  # Check we've not failed
342
- if not status and not ignore_errors and apply_failed_hosts:
343
+ if apply_failed_hosts and not status and not global_kwargs["_ignore_errors"]:
343
344
  state.fail_hosts({host})
344
345
 
345
346
  return data
@@ -33,7 +33,9 @@ if TYPE_CHECKING:
33
33
  from pyinfra.api.state import State
34
34
 
35
35
 
36
- def extract_callable_datas(datas: list[Union[Callable[..., Any], Any]]) -> Generator[Any, Any, Any]:
36
+ def extract_callable_datas(
37
+ datas: list[Union[Callable[..., Any], Any]],
38
+ ) -> Generator[Any, Any, Any]:
37
39
  for data in datas:
38
40
  # Support for dynamic data, ie @deploy wrapped data defaults where
39
41
  # the data is stored on the state temporarily.
@@ -336,12 +338,10 @@ class Host:
336
338
  T = TypeVar("T")
337
339
 
338
340
  @overload
339
- def get_fact(self, name_or_cls: Type[FactBase[T]], *args, **kwargs) -> T:
340
- ...
341
+ def get_fact(self, name_or_cls: Type[FactBase[T]], *args, **kwargs) -> T: ...
341
342
 
342
343
  @overload
343
- def get_fact(self, name_or_cls: Type[ShortFactBase[T]], *args, **kwargs) -> T:
344
- ...
344
+ def get_fact(self, name_or_cls: Type[ShortFactBase[T]], *args, **kwargs) -> T: ...
345
345
 
346
346
  def get_fact(self, name_or_cls, *args, **kwargs):
347
347
  """
@@ -37,6 +37,10 @@ class Inventory:
37
37
 
38
38
  state: "State"
39
39
 
40
+ @staticmethod
41
+ def empty():
42
+ return Inventory(([], {}))
43
+
40
44
  def __init__(self, names_data, override_data=None, **groups):
41
45
  # Setup basics
42
46
  self.groups = defaultdict(list) # lists of Host objects