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 isinstance (output , str ):
26
+ output = output .encode (get_default_encoding ())
27
+ return any (marker in output for marker in error_markers )
28
+
29
+
25
30
class LocalOperations (OsOperations ):
26
31
def __init__ (self , conn_params = None ):
27
32
if conn_params is None :
@@ -33,7 +38,38 @@ def __init__(self, conn_params=None):
33
38
self .remote = False
34
39
self .username = conn_params .username or self .get_user ()
35
40
36
- # Command execution
41
+ @staticmethod
42
+ def _run_command (cmd , shell , input , timeout , encoding , temp_file = None ):
43
+ """Execute a command and return the process."""
44
+ if temp_file is not None :
45
+ stdout = temp_file
46
+ stderr = subprocess .STDOUT
47
+ else :
48
+ stdout = subprocess .PIPE
49
+ stderr = subprocess .PIPE
50
+
51
+ process = subprocess .Popen (
52
+ cmd ,
53
+ shell = shell ,
54
+ stdin = subprocess .PIPE if input is not None else None ,
55
+ stdout = stdout ,
56
+ stderr = stderr ,
57
+ )
58
+
59
+ try :
60
+ return process .communicate (input = input .encode (encoding ) if input else None , timeout = timeout ), process
61
+ except subprocess .TimeoutExpired :
62
+ process .kill ()
63
+ raise ExecUtilException ("Command timed out after {} seconds." .format (timeout ))
64
+
65
+ @staticmethod
66
+ def _raise_exec_exception (message , command , exit_code , output ):
67
+ """Raise an ExecUtilException."""
68
+ raise ExecUtilException (message = message .format (output ),
69
+ command = command ,
70
+ exit_code = exit_code ,
71
+ out = output )
72
+
37
73
def exec_command (self , cmd , wait_exit = False , verbose = False ,
38
74
expect_error = False , encoding = None , shell = False , text = False ,
39
75
input = None , stdin = subprocess .PIPE , stdout = subprocess .PIPE , stderr = subprocess .PIPE ,
@@ -56,16 +92,15 @@ def exec_command(self, cmd, wait_exit=False, verbose=False,
56
92
:return: The output of the subprocess.
57
93
"""
58
94
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
95
+ self ._exec_command_windows (cmd , wait_exit = wait_exit , verbose = verbose ,
96
+ expect_error = expect_error , encoding = encoding , shell = shell , text = text ,
97
+ input = input , stdin = stdin , stdout = stdout , stderr = stderr ,
98
+ get_process = get_process , timeout = timeout )
65
99
else :
66
100
process = subprocess .Popen (
67
101
cmd ,
68
102
shell = shell ,
103
+ stdin = stdin ,
69
104
stdout = stdout ,
70
105
stderr = stderr ,
71
106
)
@@ -79,7 +114,7 @@ def exec_command(self, cmd, wait_exit=False, verbose=False,
79
114
raise ExecUtilException ("Command timed out after {} seconds." .format (timeout ))
80
115
exit_status = process .returncode
81
116
82
- error_found = exit_status != 0 or any ( marker in error for marker in error_markers )
117
+ error_found = exit_status != 0 or has_errors ( error )
83
118
84
119
if encoding :
85
120
result = result .decode (encoding )
@@ -91,15 +126,49 @@ def exec_command(self, cmd, wait_exit=False, verbose=False,
91
126
if exit_status != 0 or error_found :
92
127
if exit_status == 0 :
93
128
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 )
129
+ self ._raise_exec_exception ('Utility exited with non-zero code. Error `{}`' , cmd , exit_status , result )
98
130
if verbose :
99
131
return exit_status , result , error
100
132
else :
101
133
return result
102
134
135
+ @staticmethod
136
+ def _process_output (process , encoding , temp_file = None ):
137
+ """Process the output of a command."""
138
+ if temp_file is not None :
139
+ temp_file .seek (0 )
140
+ output = temp_file .read ()
141
+ else :
142
+ output = process .stdout .read ()
143
+
144
+ if encoding :
145
+ output = output .decode (encoding )
146
+
147
+ return output
148
+ def _exec_command_windows (self , cmd , wait_exit = False , verbose = False ,
149
+ expect_error = False , encoding = None , shell = False , text = False ,
150
+ input = None , stdin = subprocess .PIPE , stdout = subprocess .PIPE , stderr = subprocess .PIPE ,
151
+ get_process = None , timeout = None ):
152
+ with tempfile .NamedTemporaryFile (mode = 'w+b' ) as temp_file :
153
+ _ , process = self ._run_command (cmd , shell , input , timeout , encoding , temp_file )
154
+ if get_process :
155
+ return process
156
+ output = self ._process_output (process , encoding , temp_file )
157
+
158
+ if process .returncode != 0 or has_errors (output ):
159
+ if process .returncode == 0 :
160
+ process .returncode = 1
161
+ if expect_error :
162
+ if verbose :
163
+ return process .returncode , output , output
164
+ else :
165
+ return output
166
+ else :
167
+ self ._raise_exec_exception ('Utility exited with non-zero code. Error `{}`' , cmd , process .returncode ,
168
+ output )
169
+
170
+ return (process .returncode , output , output ) if verbose else output
171
+
103
172
# Environment setup
104
173
def environ (self , var_name ):
105
174
return os .environ .get (var_name )
@@ -210,7 +279,7 @@ def read(self, filename, encoding=None, binary=False):
210
279
if binary :
211
280
return content
212
281
if isinstance (content , bytes ):
213
- return content .decode (encoding or 'utf-8' )
282
+ return content .decode (encoding or get_default_encoding () )
214
283
return content
215
284
216
285
def readlines (self , filename , num_lines = 0 , binary = False , encoding = None ):
0 commit comments