##// END OF EJS Templates
move `--profile-dir` alias to base IPython app...
MinRK -
Show More
@@ -1,372 +1,373 b''
1 1 # encoding: utf-8
2 2 """
3 3 An application for IPython.
4 4
5 5 All top-level applications should use the classes in this module for
6 6 handling configuration and creating componenets.
7 7
8 8 The job of an :class:`Application` is to create the master configuration
9 9 object and then create the configurable objects, passing the config to them.
10 10
11 11 Authors:
12 12
13 13 * Brian Granger
14 14 * Fernando Perez
15 15 * Min RK
16 16
17 17 """
18 18
19 19 #-----------------------------------------------------------------------------
20 20 # Copyright (C) 2008-2011 The IPython Development Team
21 21 #
22 22 # Distributed under the terms of the BSD License. The full license is in
23 23 # the file COPYING, distributed as part of this software.
24 24 #-----------------------------------------------------------------------------
25 25
26 26 #-----------------------------------------------------------------------------
27 27 # Imports
28 28 #-----------------------------------------------------------------------------
29 29
30 30 import atexit
31 31 import glob
32 32 import logging
33 33 import os
34 34 import shutil
35 35 import sys
36 36
37 37 from IPython.config.application import Application, catch_config_error
38 38 from IPython.config.loader import ConfigFileNotFound
39 39 from IPython.core import release, crashhandler
40 40 from IPython.core.profiledir import ProfileDir, ProfileDirError
41 41 from IPython.utils.path import get_ipython_dir, get_ipython_package_dir
42 42 from IPython.utils.traitlets import List, Unicode, Type, Bool, Dict, Set, Instance
43 43
44 44 #-----------------------------------------------------------------------------
45 45 # Classes and functions
46 46 #-----------------------------------------------------------------------------
47 47
48 48
49 49 #-----------------------------------------------------------------------------
50 50 # Base Application Class
51 51 #-----------------------------------------------------------------------------
52 52
53 53 # aliases and flags
54 54
55 55 base_aliases = {
56 'profile-dir' : 'ProfileDir.location',
56 57 'profile' : 'BaseIPythonApplication.profile',
57 58 'ipython-dir' : 'BaseIPythonApplication.ipython_dir',
58 59 'log-level' : 'Application.log_level',
59 60 'config' : 'BaseIPythonApplication.extra_config_file',
60 61 }
61 62
62 63 base_flags = dict(
63 64 debug = ({'Application' : {'log_level' : logging.DEBUG}},
64 65 "set log level to logging.DEBUG (maximize logging output)"),
65 66 quiet = ({'Application' : {'log_level' : logging.CRITICAL}},
66 67 "set log level to logging.CRITICAL (minimize logging output)"),
67 68 init = ({'BaseIPythonApplication' : {
68 69 'copy_config_files' : True,
69 70 'auto_create' : True}
70 71 }, """Initialize profile with default config files. This is equivalent
71 72 to running `ipython profile create <profile>` prior to startup.
72 73 """)
73 74 )
74 75
75 76
76 77 class BaseIPythonApplication(Application):
77 78
78 79 name = Unicode(u'ipython')
79 80 description = Unicode(u'IPython: an enhanced interactive Python shell.')
80 81 version = Unicode(release.version)
81 82
82 83 aliases = Dict(base_aliases)
83 84 flags = Dict(base_flags)
84 85 classes = List([ProfileDir])
85 86
86 87 # Track whether the config_file has changed,
87 88 # because some logic happens only if we aren't using the default.
88 89 config_file_specified = Set()
89 90
90 91 config_file_name = Unicode()
91 92 def _config_file_name_default(self):
92 93 return self.name.replace('-','_') + u'_config.py'
93 94 def _config_file_name_changed(self, name, old, new):
94 95 if new != old:
95 96 self.config_file_specified.add(new)
96 97
97 98 # The directory that contains IPython's builtin profiles.
98 99 builtin_profile_dir = Unicode(
99 100 os.path.join(get_ipython_package_dir(), u'config', u'profile', u'default')
100 101 )
101 102
102 103 config_file_paths = List(Unicode)
103 104 def _config_file_paths_default(self):
104 105 return [os.getcwdu()]
105 106
106 107 extra_config_file = Unicode(config=True,
107 108 help="""Path to an extra config file to load.
108 109
109 110 If specified, load this config file in addition to any other IPython config.
110 111 """)
111 112 def _extra_config_file_changed(self, name, old, new):
112 113 try:
113 114 self.config_files.remove(old)
114 115 except ValueError:
115 116 pass
116 117 self.config_file_specified.add(new)
117 118 self.config_files.append(new)
118 119
119 120 profile = Unicode(u'default', config=True,
120 121 help="""The IPython profile to use."""
121 122 )
122 123
123 124 def _profile_changed(self, name, old, new):
124 125 self.builtin_profile_dir = os.path.join(
125 126 get_ipython_package_dir(), u'config', u'profile', new
126 127 )
127 128
128 129 ipython_dir = Unicode(get_ipython_dir(), config=True,
129 130 help="""
130 131 The name of the IPython directory. This directory is used for logging
131 132 configuration (through profiles), history storage, etc. The default
132 133 is usually $HOME/.ipython. This options can also be specified through
133 134 the environment variable IPYTHONDIR.
134 135 """
135 136 )
136 137 _in_init_profile_dir = False
137 138 profile_dir = Instance(ProfileDir)
138 139 def _profile_dir_default(self):
139 140 # avoid recursion
140 141 if self._in_init_profile_dir:
141 142 return
142 143 # profile_dir requested early, force initialization
143 144 self.init_profile_dir()
144 145 return self.profile_dir
145 146
146 147 overwrite = Bool(False, config=True,
147 148 help="""Whether to overwrite existing config files when copying""")
148 149 auto_create = Bool(False, config=True,
149 150 help="""Whether to create profile dir if it doesn't exist""")
150 151
151 152 config_files = List(Unicode)
152 153 def _config_files_default(self):
153 154 return [self.config_file_name]
154 155
155 156 copy_config_files = Bool(False, config=True,
156 157 help="""Whether to install the default config files into the profile dir.
157 158 If a new profile is being created, and IPython contains config files for that
158 159 profile, then they will be staged into the new directory. Otherwise,
159 160 default config files will be automatically generated.
160 161 """)
161 162
162 163 verbose_crash = Bool(False, config=True,
163 164 help="""Create a massive crash report when IPython encounters what may be an
164 165 internal error. The default is to append a short message to the
165 166 usual traceback""")
166 167
167 168 # The class to use as the crash handler.
168 169 crash_handler_class = Type(crashhandler.CrashHandler)
169 170
170 171 @catch_config_error
171 172 def __init__(self, **kwargs):
172 173 super(BaseIPythonApplication, self).__init__(**kwargs)
173 174 # ensure current working directory exists
174 175 try:
175 176 directory = os.getcwdu()
176 177 except:
177 178 # raise exception
178 179 self.log.error("Current working directory doesn't exist.")
179 180 raise
180 181
181 182 # ensure even default IPYTHONDIR exists
182 183 if not os.path.exists(self.ipython_dir):
183 184 self._ipython_dir_changed('ipython_dir', self.ipython_dir, self.ipython_dir)
184 185
185 186 #-------------------------------------------------------------------------
186 187 # Various stages of Application creation
187 188 #-------------------------------------------------------------------------
188 189
189 190 def init_crash_handler(self):
190 191 """Create a crash handler, typically setting sys.excepthook to it."""
191 192 self.crash_handler = self.crash_handler_class(self)
192 193 sys.excepthook = self.excepthook
193 194 def unset_crashhandler():
194 195 sys.excepthook = sys.__excepthook__
195 196 atexit.register(unset_crashhandler)
196 197
197 198 def excepthook(self, etype, evalue, tb):
198 199 """this is sys.excepthook after init_crashhandler
199 200
200 201 set self.verbose_crash=True to use our full crashhandler, instead of
201 202 a regular traceback with a short message (crash_handler_lite)
202 203 """
203 204
204 205 if self.verbose_crash:
205 206 return self.crash_handler(etype, evalue, tb)
206 207 else:
207 208 return crashhandler.crash_handler_lite(etype, evalue, tb)
208 209
209 210 def _ipython_dir_changed(self, name, old, new):
210 211 if old in sys.path:
211 212 sys.path.remove(old)
212 213 sys.path.append(os.path.abspath(new))
213 214 if not os.path.isdir(new):
214 215 os.makedirs(new, mode=0o777)
215 216 readme = os.path.join(new, 'README')
216 217 if not os.path.exists(readme):
217 218 path = os.path.join(get_ipython_package_dir(), u'config', u'profile')
218 219 shutil.copy(os.path.join(path, 'README'), readme)
219 220 self.log.debug("IPYTHONDIR set to: %s" % new)
220 221
221 222 def load_config_file(self, suppress_errors=True):
222 223 """Load the config file.
223 224
224 225 By default, errors in loading config are handled, and a warning
225 226 printed on screen. For testing, the suppress_errors option is set
226 227 to False, so errors will make tests fail.
227 228 """
228 229 self.log.debug("Searching path %s for config files", self.config_file_paths)
229 230 base_config = 'ipython_config.py'
230 231 self.log.debug("Attempting to load config file: %s" %
231 232 base_config)
232 233 try:
233 234 Application.load_config_file(
234 235 self,
235 236 base_config,
236 237 path=self.config_file_paths
237 238 )
238 239 except ConfigFileNotFound:
239 240 # ignore errors loading parent
240 241 self.log.debug("Config file %s not found", base_config)
241 242 pass
242 243
243 244 for config_file_name in self.config_files:
244 245 if not config_file_name or config_file_name == base_config:
245 246 continue
246 247 self.log.debug("Attempting to load config file: %s" %
247 248 self.config_file_name)
248 249 try:
249 250 Application.load_config_file(
250 251 self,
251 252 config_file_name,
252 253 path=self.config_file_paths
253 254 )
254 255 except ConfigFileNotFound:
255 256 # Only warn if the default config file was NOT being used.
256 257 if config_file_name in self.config_file_specified:
257 258 msg = self.log.warn
258 259 else:
259 260 msg = self.log.debug
260 261 msg("Config file not found, skipping: %s", config_file_name)
261 262 except:
262 263 # For testing purposes.
263 264 if not suppress_errors:
264 265 raise
265 266 self.log.warn("Error loading config file: %s" %
266 267 self.config_file_name, exc_info=True)
267 268
268 269 def init_profile_dir(self):
269 270 """initialize the profile dir"""
270 271 self._in_init_profile_dir = True
271 272 if self.profile_dir is not None:
272 273 # already ran
273 274 return
274 275 try:
275 276 # location explicitly specified:
276 277 location = self.config.ProfileDir.location
277 278 except AttributeError:
278 279 # location not specified, find by profile name
279 280 try:
280 281 p = ProfileDir.find_profile_dir_by_name(self.ipython_dir, self.profile, self.config)
281 282 except ProfileDirError:
282 283 # not found, maybe create it (always create default profile)
283 284 if self.auto_create or self.profile == 'default':
284 285 try:
285 286 p = ProfileDir.create_profile_dir_by_name(self.ipython_dir, self.profile, self.config)
286 287 except ProfileDirError:
287 288 self.log.fatal("Could not create profile: %r"%self.profile)
288 289 self.exit(1)
289 290 else:
290 291 self.log.info("Created profile dir: %r"%p.location)
291 292 else:
292 293 self.log.fatal("Profile %r not found."%self.profile)
293 294 self.exit(1)
294 295 else:
295 296 self.log.info("Using existing profile dir: %r"%p.location)
296 297 else:
297 298 # location is fully specified
298 299 try:
299 300 p = ProfileDir.find_profile_dir(location, self.config)
300 301 except ProfileDirError:
301 302 # not found, maybe create it
302 303 if self.auto_create:
303 304 try:
304 305 p = ProfileDir.create_profile_dir(location, self.config)
305 306 except ProfileDirError:
306 307 self.log.fatal("Could not create profile directory: %r"%location)
307 308 self.exit(1)
308 309 else:
309 310 self.log.info("Creating new profile dir: %r"%location)
310 311 else:
311 312 self.log.fatal("Profile directory %r not found."%location)
312 313 self.exit(1)
313 314 else:
314 315 self.log.info("Using existing profile dir: %r"%location)
315 316
316 317 self.profile_dir = p
317 318 self.config_file_paths.append(p.location)
318 319 self._in_init_profile_dir = False
319 320
320 321 def init_config_files(self):
321 322 """[optionally] copy default config files into profile dir."""
322 323 # copy config files
323 324 path = self.builtin_profile_dir
324 325 if self.copy_config_files:
325 326 src = self.profile
326 327
327 328 cfg = self.config_file_name
328 329 if path and os.path.exists(os.path.join(path, cfg)):
329 330 self.log.warn("Staging %r from %s into %r [overwrite=%s]"%(
330 331 cfg, src, self.profile_dir.location, self.overwrite)
331 332 )
332 333 self.profile_dir.copy_config_file(cfg, path=path, overwrite=self.overwrite)
333 334 else:
334 335 self.stage_default_config_file()
335 336 else:
336 337 # Still stage *bundled* config files, but not generated ones
337 338 # This is necessary for `ipython profile=sympy` to load the profile
338 339 # on the first go
339 340 files = glob.glob(os.path.join(path, '*.py'))
340 341 for fullpath in files:
341 342 cfg = os.path.basename(fullpath)
342 343 if self.profile_dir.copy_config_file(cfg, path=path, overwrite=False):
343 344 # file was copied
344 345 self.log.warn("Staging bundled %s from %s into %r"%(
345 346 cfg, self.profile, self.profile_dir.location)
346 347 )
347 348
348 349
349 350 def stage_default_config_file(self):
350 351 """auto generate default config file, and stage it into the profile."""
351 352 s = self.generate_config_file()
352 353 fname = os.path.join(self.profile_dir.location, self.config_file_name)
353 354 if self.overwrite or not os.path.exists(fname):
354 355 self.log.warn("Generating default config file: %r"%(fname))
355 356 with open(fname, 'w') as f:
356 357 f.write(s)
357 358
358 359 @catch_config_error
359 360 def initialize(self, argv=None):
360 361 # don't hook up crash handler before parsing command-line
361 362 self.parse_command_line(argv)
362 363 self.init_crash_handler()
363 364 if self.subapp is not None:
364 365 # stop here if subapp is taking over
365 366 return
366 367 cl_config = self.config
367 368 self.init_profile_dir()
368 369 self.init_config_files()
369 370 self.load_config_file()
370 371 # enforce cl-opts override configfile opts:
371 372 self.update_config(cl_config)
372 373
@@ -1,277 +1,276 b''
1 1 # encoding: utf-8
2 2 """
3 3 The Base Application class for IPython.parallel apps
4 4
5 5 Authors:
6 6
7 7 * Brian Granger
8 8 * Min RK
9 9
10 10 """
11 11
12 12 #-----------------------------------------------------------------------------
13 13 # Copyright (C) 2008-2011 The IPython Development Team
14 14 #
15 15 # Distributed under the terms of the BSD License. The full license is in
16 16 # the file COPYING, distributed as part of this software.
17 17 #-----------------------------------------------------------------------------
18 18
19 19 #-----------------------------------------------------------------------------
20 20 # Imports
21 21 #-----------------------------------------------------------------------------
22 22
23 23 from __future__ import with_statement
24 24
25 25 import os
26 26 import logging
27 27 import re
28 28 import sys
29 29
30 30 from subprocess import Popen, PIPE
31 31
32 32 from IPython.config.application import catch_config_error, LevelFormatter
33 33 from IPython.core import release
34 34 from IPython.core.crashhandler import CrashHandler
35 35 from IPython.core.application import (
36 36 BaseIPythonApplication,
37 37 base_aliases as base_ip_aliases,
38 38 base_flags as base_ip_flags
39 39 )
40 40 from IPython.utils.path import expand_path
41 41
42 42 from IPython.utils.traitlets import Unicode, Bool, Instance, Dict, List
43 43
44 44 #-----------------------------------------------------------------------------
45 45 # Module errors
46 46 #-----------------------------------------------------------------------------
47 47
48 48 class PIDFileError(Exception):
49 49 pass
50 50
51 51
52 52 #-----------------------------------------------------------------------------
53 53 # Crash handler for this application
54 54 #-----------------------------------------------------------------------------
55 55
56 56 class ParallelCrashHandler(CrashHandler):
57 57 """sys.excepthook for IPython itself, leaves a detailed report on disk."""
58 58
59 59 def __init__(self, app):
60 60 contact_name = release.authors['Min'][0]
61 61 contact_email = release.author_email
62 62 bug_tracker = 'https://github.com/ipython/ipython/issues'
63 63 super(ParallelCrashHandler,self).__init__(
64 64 app, contact_name, contact_email, bug_tracker
65 65 )
66 66
67 67
68 68 #-----------------------------------------------------------------------------
69 69 # Main application
70 70 #-----------------------------------------------------------------------------
71 71 base_aliases = {}
72 72 base_aliases.update(base_ip_aliases)
73 73 base_aliases.update({
74 'profile-dir' : 'ProfileDir.location',
75 74 'work-dir' : 'BaseParallelApplication.work_dir',
76 75 'log-to-file' : 'BaseParallelApplication.log_to_file',
77 76 'clean-logs' : 'BaseParallelApplication.clean_logs',
78 77 'log-url' : 'BaseParallelApplication.log_url',
79 78 'cluster-id' : 'BaseParallelApplication.cluster_id',
80 79 })
81 80
82 81 base_flags = {
83 82 'log-to-file' : (
84 83 {'BaseParallelApplication' : {'log_to_file' : True}},
85 84 "send log output to a file"
86 85 )
87 86 }
88 87 base_flags.update(base_ip_flags)
89 88
90 89 class BaseParallelApplication(BaseIPythonApplication):
91 90 """The base Application for IPython.parallel apps
92 91
93 92 Principle extensions to BaseIPyythonApplication:
94 93
95 94 * work_dir
96 95 * remote logging via pyzmq
97 96 * IOLoop instance
98 97 """
99 98
100 99 crash_handler_class = ParallelCrashHandler
101 100
102 101 def _log_level_default(self):
103 102 # temporarily override default_log_level to INFO
104 103 return logging.INFO
105 104
106 105 def _log_format_default(self):
107 106 """override default log format to include time"""
108 107 return u"%(asctime)s.%(msecs).03d [%(name)s]%(highlevel)s %(message)s"
109 108
110 109 work_dir = Unicode(os.getcwdu(), config=True,
111 110 help='Set the working dir for the process.'
112 111 )
113 112 def _work_dir_changed(self, name, old, new):
114 113 self.work_dir = unicode(expand_path(new))
115 114
116 115 log_to_file = Bool(config=True,
117 116 help="whether to log to a file")
118 117
119 118 clean_logs = Bool(False, config=True,
120 119 help="whether to cleanup old logfiles before starting")
121 120
122 121 log_url = Unicode('', config=True,
123 122 help="The ZMQ URL of the iplogger to aggregate logging.")
124 123
125 124 cluster_id = Unicode('', config=True,
126 125 help="""String id to add to runtime files, to prevent name collisions when
127 126 using multiple clusters with a single profile simultaneously.
128 127
129 128 When set, files will be named like: 'ipcontroller-<cluster_id>-engine.json'
130 129
131 130 Since this is text inserted into filenames, typical recommendations apply:
132 131 Simple character strings are ideal, and spaces are not recommended (but should
133 132 generally work).
134 133 """
135 134 )
136 135 def _cluster_id_changed(self, name, old, new):
137 136 self.name = self.__class__.name
138 137 if new:
139 138 self.name += '-%s'%new
140 139
141 140 def _config_files_default(self):
142 141 return ['ipcontroller_config.py', 'ipengine_config.py', 'ipcluster_config.py']
143 142
144 143 loop = Instance('zmq.eventloop.ioloop.IOLoop')
145 144 def _loop_default(self):
146 145 from zmq.eventloop.ioloop import IOLoop
147 146 return IOLoop.instance()
148 147
149 148 aliases = Dict(base_aliases)
150 149 flags = Dict(base_flags)
151 150
152 151 @catch_config_error
153 152 def initialize(self, argv=None):
154 153 """initialize the app"""
155 154 super(BaseParallelApplication, self).initialize(argv)
156 155 self.to_work_dir()
157 156 self.reinit_logging()
158 157
159 158 def to_work_dir(self):
160 159 wd = self.work_dir
161 160 if unicode(wd) != os.getcwdu():
162 161 os.chdir(wd)
163 162 self.log.info("Changing to working dir: %s" % wd)
164 163 # This is the working dir by now.
165 164 sys.path.insert(0, '')
166 165
167 166 def reinit_logging(self):
168 167 # Remove old log files
169 168 log_dir = self.profile_dir.log_dir
170 169 if self.clean_logs:
171 170 for f in os.listdir(log_dir):
172 171 if re.match(r'%s-\d+\.(log|err|out)' % self.name, f):
173 172 try:
174 173 os.remove(os.path.join(log_dir, f))
175 174 except (OSError, IOError):
176 175 # probably just conflict from sibling process
177 176 # already removing it
178 177 pass
179 178 if self.log_to_file:
180 179 # Start logging to the new log file
181 180 log_filename = self.name + u'-' + str(os.getpid()) + u'.log'
182 181 logfile = os.path.join(log_dir, log_filename)
183 182 open_log_file = open(logfile, 'w')
184 183 else:
185 184 open_log_file = None
186 185 if open_log_file is not None:
187 186 while self.log.handlers:
188 187 self.log.removeHandler(self.log.handlers[0])
189 188 self._log_handler = logging.StreamHandler(open_log_file)
190 189 self.log.addHandler(self._log_handler)
191 190 else:
192 191 self._log_handler = self.log.handlers[0]
193 192 # Add timestamps to log format:
194 193 self._log_formatter = LevelFormatter(self.log_format,
195 194 datefmt=self.log_datefmt)
196 195 self._log_handler.setFormatter(self._log_formatter)
197 196 # do not propagate log messages to root logger
198 197 # ipcluster app will sometimes print duplicate messages during shutdown
199 198 # if this is 1 (default):
200 199 self.log.propagate = False
201 200
202 201 def write_pid_file(self, overwrite=False):
203 202 """Create a .pid file in the pid_dir with my pid.
204 203
205 204 This must be called after pre_construct, which sets `self.pid_dir`.
206 205 This raises :exc:`PIDFileError` if the pid file exists already.
207 206 """
208 207 pid_file = os.path.join(self.profile_dir.pid_dir, self.name + u'.pid')
209 208 if os.path.isfile(pid_file):
210 209 pid = self.get_pid_from_file()
211 210 if not overwrite:
212 211 raise PIDFileError(
213 212 'The pid file [%s] already exists. \nThis could mean that this '
214 213 'server is already running with [pid=%s].' % (pid_file, pid)
215 214 )
216 215 with open(pid_file, 'w') as f:
217 216 self.log.info("Creating pid file: %s" % pid_file)
218 217 f.write(repr(os.getpid())+'\n')
219 218
220 219 def remove_pid_file(self):
221 220 """Remove the pid file.
222 221
223 222 This should be called at shutdown by registering a callback with
224 223 :func:`reactor.addSystemEventTrigger`. This needs to return
225 224 ``None``.
226 225 """
227 226 pid_file = os.path.join(self.profile_dir.pid_dir, self.name + u'.pid')
228 227 if os.path.isfile(pid_file):
229 228 try:
230 229 self.log.info("Removing pid file: %s" % pid_file)
231 230 os.remove(pid_file)
232 231 except:
233 232 self.log.warn("Error removing the pid file: %s" % pid_file)
234 233
235 234 def get_pid_from_file(self):
236 235 """Get the pid from the pid file.
237 236
238 237 If the pid file doesn't exist a :exc:`PIDFileError` is raised.
239 238 """
240 239 pid_file = os.path.join(self.profile_dir.pid_dir, self.name + u'.pid')
241 240 if os.path.isfile(pid_file):
242 241 with open(pid_file, 'r') as f:
243 242 s = f.read().strip()
244 243 try:
245 244 pid = int(s)
246 245 except:
247 246 raise PIDFileError("invalid pid file: %s (contents: %r)"%(pid_file, s))
248 247 return pid
249 248 else:
250 249 raise PIDFileError('pid file not found: %s' % pid_file)
251 250
252 251 def check_pid(self, pid):
253 252 if os.name == 'nt':
254 253 try:
255 254 import ctypes
256 255 # returns 0 if no such process (of ours) exists
257 256 # positive int otherwise
258 257 p = ctypes.windll.kernel32.OpenProcess(1,0,pid)
259 258 except Exception:
260 259 self.log.warn(
261 260 "Could not determine whether pid %i is running via `OpenProcess`. "
262 261 " Making the likely assumption that it is."%pid
263 262 )
264 263 return True
265 264 return bool(p)
266 265 else:
267 266 try:
268 267 p = Popen(['ps','x'], stdout=PIPE, stderr=PIPE)
269 268 output,_ = p.communicate()
270 269 except OSError:
271 270 self.log.warn(
272 271 "Could not determine whether pid %i is running via `ps x`. "
273 272 " Making the likely assumption that it is."%pid
274 273 )
275 274 return True
276 275 pids = map(int, re.findall(r'^\W*\d+', output, re.MULTILINE))
277 276 return pid in pids
General Comments 0
You need to be logged in to leave comments. Login now