shelltastic 0.4.0__tar.gz → 0.4.1__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 (21) hide show
  1. {shelltastic-0.4.0 → shelltastic-0.4.1}/PKG-INFO +1 -1
  2. {shelltastic-0.4.0 → shelltastic-0.4.1}/pyproject.toml +1 -1
  3. {shelltastic-0.4.0 → shelltastic-0.4.1}/src/shelltastic/__init__.py +2 -0
  4. shelltastic-0.4.1/src/shelltastic/backend/__init__.py +35 -0
  5. {shelltastic-0.4.0 → shelltastic-0.4.1}/src/shelltastic/backend/base.py +10 -15
  6. shelltastic-0.4.0/src/shelltastic/backend/__init__.py → shelltastic-0.4.1/src/shelltastic/backend/default/common.py +3 -56
  7. shelltastic-0.4.1/src/shelltastic/backend/default/local.py +14 -0
  8. shelltastic-0.4.1/src/shelltastic/backend/default/remote.py +65 -0
  9. shelltastic-0.4.1/src/shelltastic/frontend/__init__.py +0 -0
  10. {shelltastic-0.4.0 → shelltastic-0.4.1}/src/shelltastic/frontend/git.py +6 -6
  11. {shelltastic-0.4.0 → shelltastic-0.4.1}/src/shelltastic/frontend/scp.py +3 -3
  12. {shelltastic-0.4.0 → shelltastic-0.4.1}/src/shelltastic/frontend/shell.py +5 -4
  13. shelltastic-0.4.1/src/shelltastic/result.py +16 -0
  14. {shelltastic-0.4.0 → shelltastic-0.4.1}/LICENSE +0 -0
  15. {shelltastic-0.4.0 → shelltastic-0.4.1}/README.md +0 -0
  16. {shelltastic-0.4.0/src/shelltastic/frontend → shelltastic-0.4.1/src/shelltastic/backend/default}/__init__.py +0 -0
  17. {shelltastic-0.4.0 → shelltastic-0.4.1}/src/shelltastic/display.py +0 -0
  18. {shelltastic-0.4.0 → shelltastic-0.4.1}/src/shelltastic/enum.py +0 -0
  19. {shelltastic-0.4.0 → shelltastic-0.4.1}/src/shelltastic/exception.py +0 -0
  20. {shelltastic-0.4.0 → shelltastic-0.4.1}/src/shelltastic/frontend/common.py +0 -0
  21. {shelltastic-0.4.0 → shelltastic-0.4.1}/src/shelltastic/host.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: shelltastic
3
- Version: 0.4.0
3
+ Version: 0.4.1
4
4
  Summary: A fantastic shell command runner for python
5
5
  Author: Bearmine
6
6
  License-Expression: MPL-2.0
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "shelltastic"
3
- version = "0.4.0"
3
+ version = "0.4.1"
4
4
  description = "A fantastic shell command runner for python"
5
5
  readme = "README.md"
6
6
  authors = [
@@ -3,6 +3,7 @@ from shelltastic.frontend.git import LocalGitFrontend
3
3
  from shelltastic.frontend.scp import SCP
4
4
  from shelltastic.frontend.shell import LocalShellFrontend
5
5
  from shelltastic.host import Host
6
+ from shelltastic.result import ShellResult
6
7
 
7
8
  __all__ = [
8
9
  "shell",
@@ -12,6 +13,7 @@ __all__ = [
12
13
  "CaptureMode",
13
14
  "SystemType",
14
15
  "Host",
16
+ "ShellResult",
15
17
  ]
16
18
 
17
19
  shell = LocalShellFrontend()
@@ -0,0 +1,35 @@
1
+ from typing import Type
2
+
3
+ from shelltastic.backend.base import LocalShellBackend, RemoteShellBackend
4
+ from shelltastic.backend.default.local import DefaultLocalBackend
5
+ from shelltastic.backend.default.remote import DefaultRemoteBackend
6
+ from shelltastic.host import Host
7
+
8
+ _default_local_backend: Type[LocalShellBackend] = DefaultLocalBackend
9
+ _default_remote_backend: Type[RemoteShellBackend] = DefaultRemoteBackend
10
+
11
+
12
+ def register_default_local_backend(backend: Type[LocalShellBackend]):
13
+ global _default_local_backend
14
+ _default_local_backend = backend
15
+
16
+
17
+ def register_default_remote_backend(backend: Type[RemoteShellBackend]):
18
+ global _default_remote_backend
19
+ _default_remote_backend = backend
20
+
21
+
22
+ def get_local_backend(
23
+ *, backend: Type[LocalShellBackend] | None = None
24
+ ) -> LocalShellBackend:
25
+ if backend:
26
+ return backend.local()
27
+ return _default_local_backend.local()
28
+
29
+
30
+ def get_remote_backend(
31
+ host: Host, *, backend: Type[RemoteShellBackend] | None = None
32
+ ) -> RemoteShellBackend:
33
+ if backend:
34
+ return backend.for_host(host)
35
+ return _default_remote_backend.for_host(host)
@@ -1,26 +1,12 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from abc import ABC, abstractmethod
4
- from dataclasses import dataclass
5
4
  from pathlib import Path
6
5
 
7
6
  from shelltastic.display import IODisplay
8
7
  from shelltastic.enum import CaptureMode, DisplayMode
9
- from shelltastic.exception import ShellException
10
8
  from shelltastic.host import Host
11
-
12
-
13
- @dataclass
14
- class ShellResult:
15
- cmd: str | list[str]
16
- returncode: int
17
- stdout: bytes | None
18
- stderr: bytes | None
19
-
20
- def check_returncode(self):
21
- """If returncode is not 0, raise ShellException"""
22
- if self.returncode != 0:
23
- raise ShellException(self.cmd, self.returncode, self.stdout, self.stderr)
9
+ from shelltastic.result import ShellResult
24
10
 
25
11
 
26
12
  class ShellBackend(ABC):
@@ -43,6 +29,15 @@ class ShellBackend(ABC):
43
29
  raise NotImplementedError()
44
30
 
45
31
 
32
+ class LocalShellBackend(ShellBackend):
33
+ __slots__ = ()
34
+
35
+ @staticmethod
36
+ @abstractmethod
37
+ def local() -> LocalShellBackend:
38
+ raise NotImplementedError()
39
+
40
+
46
41
  class RemoteShellBackend(ShellBackend):
47
42
  __slots__ = ()
48
43
 
@@ -8,10 +8,9 @@ from multiprocessing.pool import ThreadPool
8
8
  from typing import IO, Literal
9
9
 
10
10
  from shelltastic import display
11
- from shelltastic.backend.base import RemoteShellBackend, ShellBackend, ShellResult
11
+ from shelltastic.backend.base import ShellBackend
12
12
  from shelltastic.enum import CaptureMode, DisplayMode
13
- from shelltastic.exception import SSHConnectionError
14
- from shelltastic.host import Host
13
+ from shelltastic.result import ShellResult
15
14
 
16
15
  LOGGER = logging.getLogger(__name__)
17
16
 
@@ -87,7 +86,7 @@ def _determine_subprocess_output_mode(
87
86
  return subprocess.PIPE
88
87
 
89
88
 
90
- class LocalShellBackend(ShellBackend):
89
+ class CommonDefaultBackend(ShellBackend):
91
90
  __slots__ = ()
92
91
 
93
92
  def run(
@@ -195,55 +194,3 @@ class LocalShellBackend(ShellBackend):
195
194
  result.check_returncode()
196
195
 
197
196
  return result
198
-
199
-
200
- class SSHShellBackend(LocalShellBackend, RemoteShellBackend):
201
- def __init__(self, host: Host) -> None:
202
- super().__init__()
203
- self.host: Host = host
204
-
205
- @staticmethod
206
- def for_host(host: Host) -> RemoteShellBackend:
207
- return SSHShellBackend(host)
208
-
209
- def run(
210
- self,
211
- cmd: str | list[str],
212
- *,
213
- subshell: bool = True,
214
- cwd: str | pathlib.Path | None = None,
215
- **kwargs,
216
- ) -> ShellResult:
217
- """
218
- Execute a remote command using SSH.
219
- """
220
- if isinstance(cmd, list):
221
- cmd = shlex.join(cmd)
222
-
223
- # Wrap user command in sh -c call
224
- if subshell:
225
- cmd = shlex.join(["bash", "-c", cmd])
226
-
227
- # cd into cwd first if cwd is set
228
- if cwd:
229
- cmd = shlex.join(["cd", str(cwd)]) + " && " + cmd
230
-
231
- port_flag = ["-p", str(self.host.port)] if self.host.port else []
232
-
233
- try:
234
- result = super().run(
235
- ["ssh", *port_flag, self.host.host_specifier(), cmd], **kwargs
236
- )
237
- if (
238
- result.returncode == 255
239
- ): # 255 is what ssh returns if it has an error with ssh itself
240
- raise SSHConnectionError(
241
- self.host.hostname, result.returncode, result.stdout, result.stderr
242
- )
243
- return result
244
- except subprocess.CalledProcessError as ex:
245
- if ex.returncode == 255:
246
- raise SSHConnectionError(
247
- self.host.hostname, ex.returncode, ex.stdout, ex.stderr
248
- ) from ex
249
- raise
@@ -0,0 +1,14 @@
1
+ from __future__ import annotations
2
+
3
+ import logging
4
+
5
+ from shelltastic.backend.base import LocalShellBackend
6
+ from shelltastic.backend.default.common import CommonDefaultBackend
7
+
8
+ LOGGER = logging.getLogger(__name__)
9
+
10
+
11
+ class DefaultLocalBackend(CommonDefaultBackend, LocalShellBackend):
12
+ @staticmethod
13
+ def local() -> LocalShellBackend:
14
+ return DefaultLocalBackend()
@@ -0,0 +1,65 @@
1
+ from __future__ import annotations
2
+
3
+ import logging
4
+ import pathlib
5
+ import shlex
6
+
7
+ from shelltastic.backend.base import RemoteShellBackend
8
+ from shelltastic.backend.default.common import CommonDefaultBackend
9
+ from shelltastic.exception import ShellException, SSHConnectionError
10
+ from shelltastic.host import Host
11
+ from shelltastic.result import ShellResult
12
+
13
+ LOGGER = logging.getLogger(__name__)
14
+
15
+
16
+ class DefaultRemoteBackend(CommonDefaultBackend, RemoteShellBackend):
17
+ def __init__(self, host: Host) -> None:
18
+ super().__init__()
19
+ self.host: Host = host
20
+
21
+ @staticmethod
22
+ def for_host(host: Host) -> RemoteShellBackend:
23
+ return DefaultRemoteBackend(host)
24
+
25
+ def run(
26
+ self,
27
+ cmd: str | list[str],
28
+ *,
29
+ subshell: bool = True,
30
+ cwd: str | pathlib.Path | None = None,
31
+ **kwargs,
32
+ ) -> ShellResult:
33
+ """
34
+ Execute a remote command using SSH.
35
+ """
36
+ if isinstance(cmd, list):
37
+ cmd = shlex.join(cmd)
38
+
39
+ # Wrap user command in sh -c call
40
+ if subshell:
41
+ cmd = shlex.join(["bash", "-c", cmd])
42
+
43
+ # cd into cwd first if cwd is set
44
+ if cwd:
45
+ cmd = shlex.join(["cd", str(cwd)]) + " && " + cmd
46
+
47
+ port_flag = ["-p", str(self.host.port)] if self.host.port else []
48
+
49
+ try:
50
+ result = super().run(
51
+ ["ssh", *port_flag, self.host.host_specifier(), cmd], **kwargs
52
+ )
53
+ if (
54
+ result.returncode == 255
55
+ ): # 255 is what ssh returns if it has an error with ssh itself
56
+ raise SSHConnectionError(
57
+ self.host.hostname, result.returncode, result.stdout, result.stderr
58
+ )
59
+ return result
60
+ except ShellException as ex:
61
+ if ex.returncode == 255:
62
+ raise SSHConnectionError(
63
+ self.host.hostname, ex.returncode, ex.stdout, ex.stderr
64
+ ) from ex
65
+ raise
File without changes
@@ -2,7 +2,7 @@ import logging
2
2
  from pathlib import Path
3
3
  from typing import overload
4
4
 
5
- from shelltastic.backend import LocalShellBackend, SSHShellBackend
5
+ from shelltastic import backend
6
6
  from shelltastic.backend.base import ShellBackend
7
7
  from shelltastic.enum import CaptureMode
8
8
  from shelltastic.frontend.common import RemoteFrontend, RemoteFrontendFactory
@@ -29,7 +29,7 @@ class GitFrontend:
29
29
  stdout=CaptureMode.PIPE,
30
30
  cwd=repo_path,
31
31
  )
32
- assert(result.stdout is not None)
32
+ assert result.stdout is not None
33
33
  return result.stdout.decode().strip()
34
34
 
35
35
  def set_remote_url(
@@ -57,7 +57,7 @@ class GitFrontend:
57
57
  stdout=CaptureMode.PIPE,
58
58
  stderr=CaptureMode.PIPE,
59
59
  )
60
- assert(result.stdout is not None)
60
+ assert result.stdout is not None
61
61
  configs = [i.strip() for i in result.stdout.decode().split("\n") if i.strip()]
62
62
  return configs
63
63
 
@@ -90,7 +90,7 @@ class GitFrontend:
90
90
  if response.returncode != 0:
91
91
  return []
92
92
  else:
93
- assert(response.stdout is not None)
93
+ assert response.stdout is not None
94
94
  gitcredentials = response.stdout.decode()
95
95
 
96
96
  return gitcredentials.splitlines()
@@ -100,7 +100,7 @@ class RemoteGitFrontend(GitFrontend, RemoteFrontend):
100
100
  __slots__ = ()
101
101
 
102
102
  def __init__(self, host: Host) -> None:
103
- super().__init__(SSHShellBackend.for_host(host))
103
+ super().__init__(backend.get_remote_backend(host))
104
104
  super(GitFrontend, self).__init__(host)
105
105
 
106
106
 
@@ -108,7 +108,7 @@ class LocalGitFrontend(GitFrontend, RemoteFrontendFactory[RemoteGitFrontend]):
108
108
  __slots__ = ()
109
109
 
110
110
  def __init__(self) -> None:
111
- super().__init__(LocalShellBackend())
111
+ super().__init__(backend.get_local_backend())
112
112
 
113
113
  def _create_remote_frontend(self, host: Host) -> RemoteGitFrontend:
114
114
  return RemoteGitFrontend(host)
@@ -1,7 +1,7 @@
1
1
  import logging
2
2
  from pathlib import Path
3
3
 
4
- from shelltastic.backend import LocalShellBackend, SSHShellBackend
4
+ from shelltastic import backend
5
5
  from shelltastic.enum import CaptureMode
6
6
  from shelltastic.frontend.common import RemoteFrontend, RemoteFrontendFactory
7
7
  from shelltastic.host import Host
@@ -12,8 +12,8 @@ LOGGER = logging.getLogger(__name__)
12
12
  class SCPFrontend(RemoteFrontend):
13
13
  def __init__(self, host: Host) -> None:
14
14
  super().__init__(host)
15
- self._local_backend = LocalShellBackend()
16
- self._remote_backend = SSHShellBackend(host)
15
+ self._local_backend = backend.get_local_backend()
16
+ self._remote_backend = backend.get_remote_backend(host)
17
17
 
18
18
  def _scp_flags(self, recursive) -> list[str]:
19
19
  # Set Port
@@ -3,12 +3,13 @@ from __future__ import annotations
3
3
  import shutil
4
4
  from pathlib import Path
5
5
 
6
- from shelltastic.backend import LocalShellBackend, SSHShellBackend
7
- from shelltastic.backend.base import ShellBackend, ShellResult
6
+ from shelltastic import backend
7
+ from shelltastic.backend.base import ShellBackend
8
8
  from shelltastic.display import IODisplay
9
9
  from shelltastic.enum import CaptureMode, DisplayMode, SystemType
10
10
  from shelltastic.frontend.common import RemoteFrontend, RemoteFrontendFactory
11
11
  from shelltastic.host import Host
12
+ from shelltastic.result import ShellResult
12
13
 
13
14
 
14
15
  class ShellFrontend:
@@ -91,7 +92,7 @@ class RemoteShellFrontend(ShellFrontend, RemoteFrontend):
91
92
  __slots__ = ()
92
93
 
93
94
  def __init__(self, host: Host) -> None:
94
- super().__init__(SSHShellBackend.for_host(host))
95
+ super().__init__(backend.get_remote_backend(host))
95
96
  super(ShellFrontend, self).__init__(host)
96
97
 
97
98
 
@@ -99,7 +100,7 @@ class LocalShellFrontend(ShellFrontend, RemoteFrontendFactory[RemoteShellFronten
99
100
  __slots__ = ()
100
101
 
101
102
  def __init__(self) -> None:
102
- super().__init__(LocalShellBackend())
103
+ super().__init__(backend.get_local_backend())
103
104
 
104
105
  def _create_remote_frontend(self, host: Host) -> RemoteShellFrontend:
105
106
  return RemoteShellFrontend(host)
@@ -0,0 +1,16 @@
1
+ from dataclasses import dataclass
2
+
3
+ from shelltastic.exception import ShellException
4
+
5
+
6
+ @dataclass
7
+ class ShellResult:
8
+ cmd: str | list[str]
9
+ returncode: int
10
+ stdout: bytes | None
11
+ stderr: bytes | None
12
+
13
+ def check_returncode(self):
14
+ """If returncode is not 0, raise ShellException"""
15
+ if self.returncode != 0:
16
+ raise ShellException(self.cmd, self.returncode, self.stdout, self.stderr)
File without changes
File without changes