8
8
import psutil
9
9
10
10
from ..exceptions import ExecUtilException
11
- from .os_ops import ConnectionParams , OsOperations
12
- from .os_ops import pglib
11
+ from .os_ops import ConnectionParams , OsOperations , pglib , get_default_encoding
13
12
14
13
try :
15
14
from shutil import which as find_executable
22
21
error_markers = [b'error' , b'Permission denied' , b'fatal' ]
23
22
24
23
24
+ def has_errors (output ):
25
+ if output :
26
+ if isinstance (output , str ):
27
+ output = output .encode (get_default_encoding ())
28
+ return any (marker in output for marker in error_markers )
29
+ return False
30
+
31
+
25
32
class LocalOperations (OsOperations ):
26
33
def __init__ (self , conn_params = None ):
27
34
if conn_params is None :
@@ -33,72 +40,80 @@ def __init__(self, conn_params=None):
33
40
self .remote = False
34
41
self .username = conn_params .username or self .get_user ()
35
42
36
- # Command execution
37
- def exec_command (self , cmd , wait_exit = False , verbose = False ,
38
- expect_error = False , encoding = None , shell = False , text = False ,
39
- input = None , stdin = subprocess .PIPE , stdout = subprocess .PIPE , stderr = subprocess .PIPE ,
40
- get_process = None , timeout = None ):
41
- """
42
- Execute a command in a subprocess.
43
-
44
- Args:
45
- - cmd: The command to execute.
46
- - wait_exit: Whether to wait for the subprocess to exit before returning.
47
- - verbose: Whether to return verbose output.
48
- - expect_error: Whether to raise an error if the subprocess exits with an error status.
49
- - encoding: The encoding to use for decoding the subprocess output.
50
- - shell: Whether to use shell when executing the subprocess.
51
- - text: Whether to return str instead of bytes for the subprocess output.
52
- - input: The input to pass to the subprocess.
53
- - stdout: The stdout to use for the subprocess.
54
- - stderr: The stderr to use for the subprocess.
55
- - proc: The process to use for subprocess creation.
56
- :return: The output of the subprocess.
57
- """
58
- if os .name == 'nt' :
59
- with tempfile .NamedTemporaryFile () as buf :
60
- process = subprocess .Popen (cmd , stdout = buf , stderr = subprocess .STDOUT )
61
- process .communicate ()
62
- buf .seek (0 )
63
- result = buf .read ().decode (encoding )
64
- return result
65
- else :
43
+ @staticmethod
44
+ def _raise_exec_exception (message , command , exit_code , output ):
45
+ """Raise an ExecUtilException."""
46
+ raise ExecUtilException (message = message .format (output ),
47
+ command = command ,
48
+ exit_code = exit_code ,
49
+ out = output )
50
+
51
+ @staticmethod
52
+ def _process_output (encoding , temp_file_path ):
53
+ """Process the output of a command from a temporary file."""
54
+ with open (temp_file_path , 'rb' ) as temp_file :
55
+ output = temp_file .read ()
56
+ if encoding :
57
+ output = output .decode (encoding )
58
+ return output , None # In Windows stderr writing in stdout
59
+
60
+ def _run_command (self , cmd , shell , input , stdin , stdout , stderr , get_process , timeout , encoding ):
61
+ """Execute a command and return the process and its output."""
62
+ if os .name == 'nt' and stdout is None : # Windows
63
+ with tempfile .NamedTemporaryFile (mode = 'w+b' , delete = False ) as temp_file :
64
+ stdout = temp_file
65
+ stderr = subprocess .STDOUT
66
+ process = subprocess .Popen (
67
+ cmd ,
68
+ shell = shell ,
69
+ stdin = stdin or subprocess .PIPE if input is not None else None ,
70
+ stdout = stdout ,
71
+ stderr = stderr ,
72
+ )
73
+ if get_process :
74
+ return process , None , None
75
+ temp_file_path = temp_file .name
76
+
77
+ # Wait process finished
78
+ process .wait ()
79
+
80
+ output , error = self ._process_output (encoding , temp_file_path )
81
+ return process , output , error
82
+ else : # Other OS
66
83
process = subprocess .Popen (
67
84
cmd ,
68
85
shell = shell ,
69
- stdout = stdout ,
70
- stderr = stderr ,
86
+ stdin = stdin or subprocess .PIPE if input is not None else None ,
87
+ stdout = stdout or subprocess .PIPE ,
88
+ stderr = stderr or subprocess .PIPE ,
71
89
)
72
90
if get_process :
73
- return process
74
-
91
+ return process , None , None
75
92
try :
76
- result , error = process .communicate (input , timeout = timeout )
93
+ output , error = process .communicate (input = input .encode (encoding ) if input else None , timeout = timeout )
94
+ if encoding :
95
+ output = output .decode (encoding )
96
+ error = error .decode (encoding )
97
+ return process , output , error
77
98
except subprocess .TimeoutExpired :
78
99
process .kill ()
79
100
raise ExecUtilException ("Command timed out after {} seconds." .format (timeout ))
80
- exit_status = process .returncode
81
-
82
- error_found = exit_status != 0 or any (marker in error for marker in error_markers )
83
101
84
- if encoding :
85
- result = result .decode (encoding )
86
- error = error .decode (encoding )
87
-
88
- if expect_error :
89
- raise Exception (result , error )
90
-
91
- if exit_status != 0 or error_found :
92
- if exit_status == 0 :
93
- exit_status = 1
94
- raise ExecUtilException (message = 'Utility exited with non-zero code. Error `{}`' .format (error ),
95
- command = cmd ,
96
- exit_code = exit_status ,
97
- out = result )
98
- if verbose :
99
- return exit_status , result , error
100
- else :
101
- return result
102
+ def exec_command (self , cmd , wait_exit = False , verbose = False , expect_error = False , encoding = None , shell = False ,
103
+ text = False , input = None , stdin = None , stdout = None , stderr = None , get_process = False , timeout = None ):
104
+ """
105
+ Execute a command in a subprocess and handle the output based on the provided parameters.
106
+ """
107
+ process , output , error = self ._run_command (cmd , shell , input , stdin , stdout , stderr , get_process , timeout , encoding )
108
+ if get_process :
109
+ return process
110
+ if process .returncode != 0 or (has_errors (error ) and not expect_error ):
111
+ self ._raise_exec_exception ('Utility exited with non-zero code. Error `{}`' , cmd , process .returncode , error )
112
+
113
+ if verbose :
114
+ return process .returncode , output , error
115
+ else :
116
+ return output
102
117
103
118
# Environment setup
104
119
def environ (self , var_name ):
@@ -210,7 +225,7 @@ def read(self, filename, encoding=None, binary=False):
210
225
if binary :
211
226
return content
212
227
if isinstance (content , bytes ):
213
- return content .decode (encoding or 'utf-8' )
228
+ return content .decode (encoding or get_default_encoding () )
214
229
return content
215
230
216
231
def readlines (self , filename , num_lines = 0 , binary = False , encoding = None ):
0 commit comments