pyinfra 3.0b1__py2.py3-none-any.whl → 3.0b2__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 (104) hide show
  1. pyinfra/api/arguments.py +9 -3
  2. pyinfra/api/arguments_typed.py +8 -5
  3. pyinfra/api/command.py +5 -3
  4. pyinfra/api/config.py +115 -13
  5. pyinfra/api/connectors.py +5 -2
  6. pyinfra/api/facts.py +33 -32
  7. pyinfra/api/host.py +5 -5
  8. pyinfra/api/inventory.py +4 -0
  9. pyinfra/api/operation.py +22 -14
  10. pyinfra/api/util.py +24 -16
  11. pyinfra/connectors/base.py +3 -6
  12. pyinfra/connectors/docker.py +2 -9
  13. pyinfra/connectors/local.py +2 -2
  14. pyinfra/connectors/ssh.py +2 -2
  15. pyinfra/connectors/util.py +6 -7
  16. pyinfra/connectors/vagrant.py +5 -5
  17. pyinfra/context.py +1 -0
  18. pyinfra/facts/apk.py +2 -0
  19. pyinfra/facts/apt.py +2 -0
  20. pyinfra/facts/brew.py +2 -0
  21. pyinfra/facts/bsdinit.py +2 -0
  22. pyinfra/facts/cargo.py +2 -0
  23. pyinfra/facts/choco.py +2 -0
  24. pyinfra/facts/deb.py +7 -2
  25. pyinfra/facts/dnf.py +2 -0
  26. pyinfra/facts/docker.py +2 -0
  27. pyinfra/facts/files.py +2 -0
  28. pyinfra/facts/gem.py +2 -0
  29. pyinfra/facts/gpg.py +2 -0
  30. pyinfra/facts/hardware.py +30 -22
  31. pyinfra/facts/launchd.py +2 -0
  32. pyinfra/facts/lxd.py +2 -0
  33. pyinfra/facts/mysql.py +12 -6
  34. pyinfra/facts/npm.py +1 -0
  35. pyinfra/facts/openrc.py +2 -0
  36. pyinfra/facts/pacman.py +6 -2
  37. pyinfra/facts/pip.py +2 -0
  38. pyinfra/facts/pkg.py +2 -0
  39. pyinfra/facts/pkgin.py +2 -0
  40. pyinfra/facts/postgres.py +6 -6
  41. pyinfra/facts/postgresql.py +2 -0
  42. pyinfra/facts/rpm.py +12 -9
  43. pyinfra/facts/server.py +10 -13
  44. pyinfra/facts/snap.py +2 -0
  45. pyinfra/facts/systemd.py +2 -0
  46. pyinfra/facts/upstart.py +2 -0
  47. pyinfra/facts/util/packaging.py +3 -2
  48. pyinfra/facts/vzctl.py +2 -0
  49. pyinfra/facts/xbps.py +2 -0
  50. pyinfra/facts/yum.py +2 -0
  51. pyinfra/facts/zypper.py +2 -0
  52. pyinfra/operations/apk.py +3 -1
  53. pyinfra/operations/apt.py +16 -18
  54. pyinfra/operations/brew.py +10 -8
  55. pyinfra/operations/bsdinit.py +5 -3
  56. pyinfra/operations/cargo.py +3 -1
  57. pyinfra/operations/choco.py +3 -1
  58. pyinfra/operations/dnf.py +15 -19
  59. pyinfra/operations/files.py +81 -66
  60. pyinfra/operations/gem.py +3 -1
  61. pyinfra/operations/git.py +18 -16
  62. pyinfra/operations/iptables.py +27 -25
  63. pyinfra/operations/launchd.py +5 -6
  64. pyinfra/operations/lxd.py +7 -4
  65. pyinfra/operations/mysql.py +57 -53
  66. pyinfra/operations/npm.py +8 -1
  67. pyinfra/operations/openrc.py +5 -3
  68. pyinfra/operations/pacman.py +4 -5
  69. pyinfra/operations/pip.py +11 -9
  70. pyinfra/operations/pkg.py +3 -1
  71. pyinfra/operations/pkgin.py +3 -1
  72. pyinfra/operations/postgres.py +39 -37
  73. pyinfra/operations/postgresql.py +2 -0
  74. pyinfra/operations/puppet.py +3 -1
  75. pyinfra/operations/python.py +7 -3
  76. pyinfra/operations/selinux.py +42 -16
  77. pyinfra/operations/server.py +48 -43
  78. pyinfra/operations/snap.py +3 -1
  79. pyinfra/operations/ssh.py +12 -10
  80. pyinfra/operations/systemd.py +8 -6
  81. pyinfra/operations/sysvinit.py +6 -4
  82. pyinfra/operations/upstart.py +5 -3
  83. pyinfra/operations/util/files.py +24 -16
  84. pyinfra/operations/util/packaging.py +53 -37
  85. pyinfra/operations/util/service.py +18 -13
  86. pyinfra/operations/vzctl.py +12 -10
  87. pyinfra/operations/xbps.py +3 -1
  88. pyinfra/operations/yum.py +14 -18
  89. pyinfra/operations/zypper.py +8 -9
  90. pyinfra/version.py +5 -2
  91. {pyinfra-3.0b1.dist-info → pyinfra-3.0b2.dist-info}/METADATA +28 -26
  92. pyinfra-3.0b2.dist-info/RECORD +163 -0
  93. {pyinfra-3.0b1.dist-info → pyinfra-3.0b2.dist-info}/WHEEL +1 -1
  94. pyinfra_cli/exceptions.py +0 -5
  95. pyinfra_cli/inventory.py +38 -19
  96. pyinfra_cli/prints.py +15 -11
  97. pyinfra_cli/util.py +3 -1
  98. tests/test_api/test_api_operations.py +1 -1
  99. tests/test_connectors/test_ssh.py +66 -13
  100. tests/test_connectors/test_vagrant.py +3 -3
  101. pyinfra-3.0b1.dist-info/RECORD +0 -163
  102. {pyinfra-3.0b1.dist-info → pyinfra-3.0b2.dist-info}/LICENSE.md +0 -0
  103. {pyinfra-3.0b1.dist-info → pyinfra-3.0b2.dist-info}/entry_points.txt +0 -0
  104. {pyinfra-3.0b1.dist-info → pyinfra-3.0b2.dist-info}/top_level.txt +0 -0
pyinfra/operations/apt.py CHANGED
@@ -2,6 +2,8 @@
2
2
  Manage apt packages and repositories.
3
3
  """
4
4
 
5
+ from __future__ import annotations
6
+
5
7
  from datetime import timedelta
6
8
  from urllib.parse import urlparse
7
9
 
@@ -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:
@@ -37,7 +39,7 @@ def noninteractive_apt(command, force=False):
37
39
 
38
40
 
39
41
  @operation()
40
- def key(src=None, keyserver=None, keyid=None):
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
 
@@ -103,7 +105,7 @@ def key(src=None, keyserver=None, keyid=None):
103
105
 
104
106
 
105
107
  @operation()
106
- def repo(src, present=True, filename=None):
108
+ def repo(src: str, present=True, filename: str | None = None):
107
109
  """
108
110
  Add/remove apt repositories.
109
111
 
@@ -162,7 +164,7 @@ def repo(src, present=True, filename=None):
162
164
 
163
165
 
164
166
  @operation(is_idempotent=False)
165
- def ppa(src, present=True):
167
+ def ppa(src: str, present=True):
166
168
  """
167
169
  Add/remove Ubuntu ppa repositories.
168
170
 
@@ -192,7 +194,7 @@ def ppa(src, present=True):
192
194
 
193
195
 
194
196
  @operation()
195
- def deb(src, present=True, force=False):
197
+ def deb(src: str, present=True, force=False):
196
198
  """
197
199
  Add/remove ``.deb`` file packages.
198
200
 
@@ -233,18 +235,14 @@ def deb(src, present=True, force=False):
233
235
  src = temp_filename
234
236
 
235
237
  # Check for file .deb information (if file is present)
236
- info = host.get_fact(DebPackage, name=src)
238
+ info = host.get_fact(DebPackage, package=src)
237
239
  current_packages = host.get_fact(DebPackages)
238
240
 
239
241
  exists = False
240
242
 
241
243
  # We have deb info! Check against installed packages
242
- if info:
243
- if (
244
- info["name"] in current_packages
245
- and info.get("version") in current_packages[info["name"]]
246
- ):
247
- exists = True
244
+ if info and info.get("version") in current_packages.get(info.get("name"), {}):
245
+ exists = True
248
246
 
249
247
  # Package does not exist and we want?
250
248
  if present:
@@ -277,7 +275,7 @@ def deb(src, present=True, force=False):
277
275
  "unless the ``cache_time`` argument is provided."
278
276
  ),
279
277
  )
280
- def update(cache_time=None):
278
+ def update(cache_time: int | None = None):
281
279
  """
282
280
  Updates apt repositories.
283
281
 
@@ -335,7 +333,7 @@ def upgrade(auto_remove: bool = False):
335
333
 
336
334
  # Upgrade all packages and remove unneeded transitive dependencies
337
335
  apt.upgrade(
338
- name="Upgrade apt packages and remove unneeded dependencies"
336
+ name="Upgrade apt packages and remove unneeded dependencies",
339
337
  auto_remove=True
340
338
  )
341
339
  """
@@ -370,17 +368,17 @@ def dist_upgrade():
370
368
 
371
369
  @operation()
372
370
  def packages(
373
- packages=None,
371
+ packages: str | list[str] | None = None,
374
372
  present=True,
375
373
  latest=False,
376
374
  update=False,
377
- cache_time=None,
375
+ cache_time: int | None = None,
378
376
  upgrade=False,
379
377
  force=False,
380
378
  no_recommends=False,
381
379
  allow_downgrades=False,
382
- extra_install_args=None,
383
- extra_uninstall_args=None,
380
+ extra_install_args: str | None = None,
381
+ extra_uninstall_args: str | None = None,
384
382
  ):
385
383
  """
386
384
  Install/remove/update packages & update apt.
@@ -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
@@ -37,7 +39,7 @@ _upgrade = upgrade # noqa: E305
37
39
 
38
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,
@@ -93,7 +95,7 @@ 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
 
@@ -103,12 +105,12 @@ def cask_upgrade():
103
105
  Upgrades all brew casks.
104
106
  """
105
107
 
106
- yield "brew %supgrade%s" % cask_args(host)
108
+ yield "brew %supgrade%s" % cask_args()
107
109
 
108
110
 
109
111
  @operation()
110
112
  def casks(
111
- casks=None,
113
+ casks: str | list[str] | None = None,
112
114
  present=True,
113
115
  latest=False,
114
116
  upgrade=False,
@@ -140,7 +142,7 @@ def casks(
140
142
  if upgrade:
141
143
  yield from cask_upgrade._inner()
142
144
 
143
- args = cask_args(host)
145
+ args = cask_args()
144
146
 
145
147
  yield from ensure_packages(
146
148
  host,
@@ -156,7 +158,7 @@ def casks(
156
158
 
157
159
 
158
160
  @operation()
159
- def tap(src=None, present=True, url=None):
161
+ def tap(src: str | None = None, present=True, url: str | None = None):
160
162
  """
161
163
  Add/remove brew taps.
162
164
 
@@ -199,7 +201,7 @@ def tap(src=None, present=True, url=None):
199
201
  host.noop("no tap was specified")
200
202
  return
201
203
 
202
- src = src or urllib.parse.urlparse(url).path.strip("/")
204
+ src = src or str(urllib.parse.urlparse(url).path).strip("/")
203
205
 
204
206
  if len(src.split("/")) != 2:
205
207
  host.noop("src '{0}' doesn't have two components.".format(src))
@@ -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
@@ -13,12 +15,12 @@ from .util.service import handle_service_control
13
15
 
14
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.
@@ -2,6 +2,8 @@
2
2
  Manage cargo (aka Rust) 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.cargo import CargoPackages
@@ -10,7 +12,7 @@ from .util.packaging import ensure_packages
10
12
 
11
13
 
12
14
  @operation()
13
- def packages(packages=None, present=True, latest=False):
15
+ def packages(packages: str | list[str] | None = None, present=True, latest=False):
14
16
  """
15
17
  Install/remove/update cargo packages.
16
18
 
@@ -2,6 +2,8 @@
2
2
  Manage ``choco`` (Chocolatey) packages (https://chocolatey.org).
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.choco import ChocoPackages
@@ -10,7 +12,7 @@ from .util.packaging import ensure_packages
10
12
 
11
13
 
12
14
  @operation()
13
- def packages(packages=None, present=True, latest=False):
15
+ def packages(packages: str | list[str] | None = None, present=True, latest=False):
14
16
  """
15
17
  Add/remove/update ``choco`` packages.
16
18
 
pyinfra/operations/dnf.py CHANGED
@@ -2,16 +2,17 @@
2
2
  Manage dnf packages and repositories. Note that dnf package names are case-sensitive.
3
3
  """
4
4
 
5
+ from __future__ import annotations
6
+
5
7
  from pyinfra import host, state
6
8
  from pyinfra.api import operation
7
9
  from pyinfra.facts.rpm import RpmPackageProvides, RpmPackages
8
10
 
9
- from . import files
10
11
  from .util.packaging import ensure_packages, ensure_rpm, ensure_yum_repo
11
12
 
12
13
 
13
14
  @operation(is_idempotent=False)
14
- def key(src):
15
+ def key(src: str):
15
16
  """
16
17
  Add dnf gpg keys with ``rpm``.
17
18
 
@@ -37,19 +38,19 @@ def key(src):
37
38
 
38
39
  @operation()
39
40
  def repo(
40
- src,
41
+ src: str,
41
42
  present=True,
42
- baseurl=None,
43
- description=None,
43
+ baseurl: str | None = None,
44
+ description: str | None = None,
44
45
  enabled=True,
45
46
  gpgcheck=True,
46
- gpgkey=None,
47
+ gpgkey: str | None = None,
47
48
  ):
48
49
  # NOTE: if updating this docstring also update `yum.repo`
49
50
  """
50
51
  Add/remove/update dnf repositories.
51
52
 
52
- + name: URL or name for the ``.repo`` file
53
+ + src: URL or name for the ``.repo`` file
53
54
  + present: whether the ``.repo`` file should be present
54
55
  + baseurl: the baseurl of the repo (if ``name`` is not a URL)
55
56
  + description: optional verbose description
@@ -81,9 +82,7 @@ def repo(
81
82
  """
82
83
 
83
84
  yield from ensure_yum_repo(
84
- state,
85
85
  host,
86
- files,
87
86
  src,
88
87
  baseurl,
89
88
  present,
@@ -95,7 +94,7 @@ def repo(
95
94
 
96
95
 
97
96
  @operation()
98
- def rpm(src, present=True):
97
+ def rpm(src: str, present=True):
99
98
  # NOTE: if updating this docstring also update `yum.rpm`
100
99
  """
101
100
  Add/remove ``.rpm`` file packages.
@@ -118,7 +117,7 @@ def rpm(src, present=True):
118
117
  )
119
118
  """
120
119
 
121
- yield from ensure_rpm(state, host, files, src, present, "dnf")
120
+ yield from ensure_rpm(state, host, src, present, "dnf")
122
121
 
123
122
 
124
123
  @operation(is_idempotent=False)
@@ -135,19 +134,19 @@ _update = update._inner # noqa: E305 (for use below where update is a kwarg)
135
134
 
136
135
  @operation()
137
136
  def packages(
138
- packages=None,
137
+ packages: str | list[str] | None = None,
139
138
  present=True,
140
139
  latest=False,
141
140
  update=False,
142
141
  clean=False,
143
142
  nobest=False,
144
- extra_install_args=None,
145
- extra_uninstall_args=None,
143
+ extra_install_args: str | None = None,
144
+ extra_uninstall_args: str | None = None,
146
145
  ):
147
146
  """
148
147
  Install/remove/update dnf packages & updates.
149
148
 
150
- + packages: list of packages to ensure
149
+ + packages: packages to ensure
151
150
  + present: whether the packages should be installed
152
151
  + latest: whether to upgrade packages without a specified version
153
152
  + update: run ``dnf update`` before installing packages
@@ -207,8 +206,5 @@ def packages(
207
206
  upgrade_command="dnf update -y",
208
207
  version_join="=",
209
208
  latest=latest,
210
- expand_package_fact=lambda package: host.get_fact(
211
- RpmPackageProvides,
212
- name=package,
213
- ),
209
+ expand_package_fact=lambda package: host.get_fact(RpmPackageProvides, package=package),
214
210
  )
@@ -12,7 +12,7 @@ from datetime import timedelta
12
12
  from fnmatch import fnmatch
13
13
  from io import StringIO
14
14
  from pathlib import Path
15
- from typing import Union
15
+ from typing import IO, Any, Union
16
16
 
17
17
  from jinja2 import TemplateRuntimeError, TemplateSyntaxError, UndefinedError
18
18
 
@@ -60,19 +60,19 @@ from .util.files import adjust_regex, ensure_mode_int, get_timestamp, sed_replac
60
60
 
61
61
  @operation()
62
62
  def download(
63
- src,
64
- dest,
65
- user=None,
66
- group=None,
67
- mode=None,
68
- cache_time=None,
63
+ src: str,
64
+ dest: str,
65
+ user: str | None = None,
66
+ group: str | None = None,
67
+ mode: str | None = None,
68
+ cache_time: int | None = None,
69
69
  force=False,
70
- sha256sum=None,
71
- sha1sum=None,
72
- md5sum=None,
73
- headers=None,
70
+ sha256sum: str | None = None,
71
+ sha1sum: str | None = None,
72
+ md5sum: str | None = None,
73
+ headers: dict[str, str] | None = None,
74
74
  insecure=False,
75
- proxy=None,
75
+ proxy: str | None = None,
76
76
  ):
77
77
  """
78
78
  Download files from remote locations using ``curl`` or ``wget``.
@@ -123,8 +123,8 @@ def download(
123
123
  if cache_time:
124
124
  # Time on files is not tz-aware, and will be the same tz as the server's time,
125
125
  # so we can safely remove the tzinfo from the Date fact before comparison.
126
- cache_time = host.get_fact(Date).replace(tzinfo=None) - timedelta(seconds=cache_time)
127
- if info["mtime"] and info["mtime"] < cache_time:
126
+ ctime = host.get_fact(Date).replace(tzinfo=None) - timedelta(seconds=cache_time)
127
+ if info["mtime"] and info["mtime"] < ctime:
128
128
  download = True
129
129
 
130
130
  if sha1sum:
@@ -227,11 +227,11 @@ def download(
227
227
 
228
228
  @operation()
229
229
  def line(
230
- path,
231
- line,
230
+ path: str,
231
+ line: str,
232
232
  present=True,
233
- replace=None,
234
- flags=None,
233
+ replace: str | None = None,
234
+ flags: list[str] | None = None,
235
235
  backup=False,
236
236
  interpolate_variables=False,
237
237
  escape_regex_characters=False,
@@ -269,7 +269,7 @@ def line(
269
269
  it will be append to the end of the file.
270
270
 
271
271
  Ensure new line:
272
- This will ensure that the ``line`` being appended is always on a seperate new
272
+ This will ensure that the ``line`` being appended is always on a separate new
273
273
  line in case the file doesn't end with a newline character.
274
274
 
275
275
 
@@ -427,10 +427,10 @@ def line(
427
427
 
428
428
  @operation()
429
429
  def replace(
430
- path,
431
- text=None,
432
- replace=None,
433
- flags=None,
430
+ path: str,
431
+ text: str | None = None,
432
+ replace: str | None = None,
433
+ flags: list[str] | None = None,
434
434
  backup=False,
435
435
  interpolate_variables=False,
436
436
  match=None, # deprecated
@@ -501,15 +501,15 @@ def replace(
501
501
 
502
502
  @operation()
503
503
  def sync(
504
- src,
505
- dest,
506
- user=None,
507
- group=None,
508
- mode=None,
509
- dir_mode=None,
504
+ src: str,
505
+ dest: str,
506
+ user: str | None = None,
507
+ group: str | None = None,
508
+ mode: str | None = None,
509
+ dir_mode: str | None = None,
510
510
  delete=False,
511
- exclude=None,
512
- exclude_dir=None,
511
+ exclude: str | list[str] | tuple[str] | None = None,
512
+ exclude_dir: str | list[str] | tuple[str] | None = None,
513
513
  add_deploy_dir=True,
514
514
  ):
515
515
  """
@@ -652,7 +652,7 @@ def show_rsync_warning():
652
652
 
653
653
 
654
654
  @operation(is_idempotent=False)
655
- def rsync(src, dest, flags=["-ax", "--delete"]):
655
+ def rsync(src: str, dest: str, flags: list[str] | None = None):
656
656
  """
657
657
  Use ``rsync`` to sync a local directory to the remote system. This operation will actually call
658
658
  the ``rsync`` binary on your system.
@@ -667,6 +667,8 @@ def rsync(src, dest, flags=["-ax", "--delete"]):
667
667
  global arguments.
668
668
  """
669
669
 
670
+ if flags is None:
671
+ flags = ["-ax", "--delete"]
670
672
  show_rsync_warning()
671
673
 
672
674
  try:
@@ -696,8 +698,8 @@ def _create_remote_dir(remote_filename, user, group):
696
698
  is_idempotent=False,
697
699
  )
698
700
  def get(
699
- src,
700
- dest,
701
+ src: str,
702
+ dest: str,
701
703
  add_deploy_dir=True,
702
704
  create_local_dir=False,
703
705
  force=False,
@@ -756,11 +758,11 @@ def get(
756
758
 
757
759
  @operation()
758
760
  def put(
759
- src,
760
- dest,
761
- user=None,
762
- group=None,
763
- mode=None,
761
+ src: str | IO[Any],
762
+ dest: str,
763
+ user: str | None = None,
764
+ group: str | None = None,
765
+ mode: int | str | bool | None = None,
764
766
  add_deploy_dir=True,
765
767
  create_remote_dir=True,
766
768
  force=False,
@@ -821,6 +823,7 @@ def put(
821
823
 
822
824
  # Assume string filename
823
825
  else:
826
+ assert isinstance(src, (str, Path))
824
827
  # Add deploy directory?
825
828
  if add_deploy_dir and state.cwd:
826
829
  src = os.path.join(state.cwd, src)
@@ -835,7 +838,7 @@ def put(
835
838
  raise IOError("No such file: {0}".format(local_file))
836
839
 
837
840
  if mode is True:
838
- if os.path.isfile(local_file):
841
+ if isinstance(local_file, str) and os.path.isfile(local_file):
839
842
  mode = get_path_permissions_mode(local_file)
840
843
  else:
841
844
  logger.warning(
@@ -849,6 +852,7 @@ def put(
849
852
  remote_file = host.get_fact(File, path=dest)
850
853
 
851
854
  if not remote_file and bool(host.get_fact(Directory, path=dest)):
855
+ assert isinstance(src, str)
852
856
  dest = unix_path_join(dest, os.path.basename(src))
853
857
  remote_file = host.get_fact(File, path=dest)
854
858
 
@@ -905,7 +909,15 @@ def put(
905
909
 
906
910
 
907
911
  @operation()
908
- def template(src, dest, user=None, group=None, mode=None, create_remote_dir=True, **data):
912
+ def template(
913
+ src: str | IO[Any],
914
+ dest: str,
915
+ user: str | None = None,
916
+ group: str | None = None,
917
+ mode: str | None = None,
918
+ create_remote_dir=True,
919
+ **data,
920
+ ):
909
921
  '''
910
922
  Generate a template using jinja2 and write it to the remote system.
911
923
 
@@ -1055,16 +1067,16 @@ def _raise_or_remove_invalid_path(fs_type, path, force, force_backup, force_back
1055
1067
 
1056
1068
  @operation()
1057
1069
  def link(
1058
- path,
1059
- target=None,
1070
+ path: str,
1071
+ target: str | None = None,
1060
1072
  present=True,
1061
- user=None,
1062
- group=None,
1073
+ user: str | None = None,
1074
+ group: str | None = None,
1063
1075
  symbolic=True,
1064
1076
  create_remote_dir=True,
1065
1077
  force=False,
1066
1078
  force_backup=True,
1067
- force_backup_dir=None,
1079
+ force_backup_dir: str | None = None,
1068
1080
  ):
1069
1081
  """
1070
1082
  Add/remove/update links.
@@ -1162,16 +1174,16 @@ def link(
1162
1174
 
1163
1175
  @operation()
1164
1176
  def file(
1165
- path,
1177
+ path: str,
1166
1178
  present=True,
1167
- user=None,
1168
- group=None,
1169
- mode=None,
1179
+ user: str | None = None,
1180
+ group: str | None = None,
1181
+ mode: int | str | None = None,
1170
1182
  touch=False,
1171
1183
  create_remote_dir=True,
1172
1184
  force=False,
1173
1185
  force_backup=True,
1174
- force_backup_dir=None,
1186
+ force_backup_dir: str | None = None,
1175
1187
  ):
1176
1188
  """
1177
1189
  Add/remove/update files.
@@ -1264,15 +1276,15 @@ def file(
1264
1276
 
1265
1277
  @operation()
1266
1278
  def directory(
1267
- path,
1279
+ path: str,
1268
1280
  present=True,
1269
- user=None,
1270
- group=None,
1271
- mode=None,
1281
+ user: str | None = None,
1282
+ group: str | None = None,
1283
+ mode: int | str | None = None,
1272
1284
  recursive=False,
1273
1285
  force=False,
1274
1286
  force_backup=True,
1275
- force_backup_dir=None,
1287
+ force_backup_dir: str | None = None,
1276
1288
  _no_check_owner_mode=False,
1277
1289
  _no_fail_on_link=False,
1278
1290
  ):
@@ -1365,7 +1377,7 @@ def directory(
1365
1377
 
1366
1378
 
1367
1379
  @operation()
1368
- def flags(path, flags=None, present=True):
1380
+ def flags(path: str, flags: list[str] | None = None, present=True):
1369
1381
  """
1370
1382
  Set/clear file flags.
1371
1383
 
@@ -1414,18 +1426,18 @@ def flags(path, flags=None, present=True):
1414
1426
 
1415
1427
  @operation()
1416
1428
  def block(
1417
- path,
1418
- content=None,
1429
+ path: str,
1430
+ content: str | list[str] | None = None,
1419
1431
  present=True,
1420
- line=None,
1432
+ line: str | None = None,
1421
1433
  backup=False,
1422
1434
  escape_regex_characters=False,
1423
1435
  try_prevent_shell_expansion=False,
1424
1436
  before=False,
1425
1437
  after=False,
1426
- marker=None,
1427
- begin=None,
1428
- end=None,
1438
+ marker: str | None = None,
1439
+ begin: str | None = None,
1440
+ end: str | None = None,
1429
1441
  ):
1430
1442
  """
1431
1443
  Ensure content, surrounded by the appropriate markers, is present (or not) in the file.
@@ -1573,6 +1585,7 @@ def block(
1573
1585
  f"\n{the_block}\n{here}",
1574
1586
  )
1575
1587
  elif current == []: # markers not found and have a pattern to match (not start or end)
1588
+ assert isinstance(line, str)
1576
1589
  regex = adjust_regex(line, escape_regex_characters)
1577
1590
  print_before = "{ print }" if before else ""
1578
1591
  print_after = "{ print }" if after else ""
@@ -1601,9 +1614,11 @@ def block(
1601
1614
  out_prep,
1602
1615
  prog,
1603
1616
  q_path,
1604
- '"' + "\n".join(content) + '"'
1605
- if not try_prevent_shell_expansion
1606
- else "'" + "\n".join(content) + "'",
1617
+ (
1618
+ '"' + "\n".join(content) + '"'
1619
+ if not try_prevent_shell_expansion
1620
+ else "'" + "\n".join(content) + "'"
1621
+ ),
1607
1622
  "> $OUT &&",
1608
1623
  real_out,
1609
1624
  )
pyinfra/operations/gem.py CHANGED
@@ -2,6 +2,8 @@
2
2
  Manage Ruby gem packages. (see https://rubygems.org/ )
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.gem import GemPackages
@@ -10,7 +12,7 @@ from .util.packaging import ensure_packages
10
12
 
11
13
 
12
14
  @operation()
13
- def packages(packages=None, present=True, latest=False):
15
+ def packages(packages: str | list[str] | None = None, present=True, latest=False):
14
16
  """
15
17
  Add/remove/update gem packages.
16
18