root/MGET/Trunk/PythonPackage/src/GeoEco/Logging.py

Revision 386, 65.8 kB (checked in by jjr8, 7 months ago)

Merged [380-385] from Jason branch into Trunk. This will be released as MGET 0.7a13.

Line 
1 # Logging.py - Implements the Logger class, which other classes in the GeoEco
2 # Python package use to report activity to the user.
3 #
4 # Copyright (C) 2007 Jason J. Roberts
5 #
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; either version 2
9 # of the License, or (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 # GNU General Public License (available in the file LICENSE.TXT)
15 # for more details.
16 #
17 # You should have received a copy of the GNU General Public License
18 # along with this program; if not, write to the Free Software
19 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
20
21 import datetime
22 import logging
23 import logging.config       # Do not remove this statement, even though it seems redundant after importing the logging module above. Removing it will break something.
24 import os
25 import sys
26 import time
27 import traceback
28 import types
29
30 from GeoEco.DynamicDocString import DynamicDocString
31 from GeoEco.Internationalization import _, UnicodeToUserPreferredEncoding, UserPreferredEncodingToUnicode
32
33
34 # Public classes exposed by this module
35
36 class Logger(object):
37     __doc__ = DynamicDocString()
38
39     _LogInfoAsDebug = False   
40     _LogErrorsAsWarnings = False   
41
42     @classmethod
43     def GetLogInfoAsDebug(cls):
44         return Logger._LogInfoAsDebug
45
46     @classmethod
47     def SetLogInfoAsDebug(cls, value):
48         if not isinstance(value, types.BooleanType):
49             RaiseException(TypeError(_(u'The value provided for the value parameter is an invalid type ("%(badType)s" in Python). Please provide a value having the Python type "bool".') % {u'badType' : type(value).__name__}))
50         Logger._LogInfoAsDebug = value
51
52     LogInfoAsDebug = property(GetLogInfoAsDebug, SetLogInfoAsDebug, doc=DynamicDocString())
53
54     @classmethod
55     def GetLogErrorsAsWarnings(cls):
56         return Logger._LogErrorsAsWarnings
57
58     @classmethod
59     def SetLogErrorsAsWarnings(cls, value):
60         if not isinstance(value, types.BooleanType):
61             RaiseException(TypeError(_(u'The value provided for the value parameter is an invalid type ("%(badType)s" in Python). Please provide a value having the Python type "bool".') % {u'badType' : type(value).__name__}))
62         Logger._LogErrorsAsWarnings = value
63
64     LogErrorsAsWarnings = property(GetLogErrorsAsWarnings, SetLogErrorsAsWarnings, doc=DynamicDocString())
65    
66     @classmethod
67     def Debug(cls, format, *args):
68         try:
69             logging.getLogger(u'GeoEco').debug(format.rstrip(), *args)
70         except:
71             pass
72
73     @classmethod
74     def Info(cls, format, *args):
75         try:
76             if cls.GetLogInfoAsDebug():
77                 logging.getLogger(u'GeoEco').debug(format.rstrip(), *args)
78             else:
79                 logging.getLogger(u'GeoEco').info(format.rstrip(), *args)
80         except:
81             pass
82
83     @classmethod
84     def Warning(cls, format, *args):
85         try:
86             logging.getLogger(u'GeoEco').warning(format.rstrip(), *args)
87         except:
88             pass
89
90     @classmethod
91     def Error(cls, format, *args):
92         try:
93             if cls.GetLogErrorsAsWarnings():
94                 logging.getLogger(u'GeoEco').warning(format.rstrip(), *args)
95             else:
96                 logging.getLogger(u'GeoEco').error(format.rstrip(), *args)
97         except:
98             pass
99
100     @classmethod
101     def RaiseException(cls, exception):
102         try:
103             raise exception
104         except:
105             if cls.GetLogErrorsAsWarnings():
106                 cls.LogExceptionAsWarning()
107             else:
108                 cls.LogExceptionAsError()
109             raise
110
111     @classmethod
112     def LogInfoAndSetInfoToDebug(cls, format, *args):
113         cls.Info(format, *args)
114         oldValue = cls.GetLogInfoAsDebug()
115         cls.SetLogInfoAsDebug(True)
116         return oldValue
117
118     @classmethod
119     def LogExceptionAsWarning(cls, format=None, *args):
120         cls._LogExceptionAndMessage(logging.WARNING, format, *args)
121
122     @classmethod
123     def LogExceptionAsError(cls, format=None, *args):
124         if cls.GetLogErrorsAsWarnings():
125             cls._LogExceptionAndMessage(logging.WARNING, format, *args)
126         else:
127             cls._LogExceptionAndMessage(logging.ERROR, format, *args)
128
129     _ExceptionTracebackID = None
130     _ReportedSubsequentErrors = False
131    
132     @classmethod
133     def _LogExceptionAndMessage(cls, level, format=None, *args):
134         try:
135             logger = logging.getLogger(u'GeoEco')
136
137             # Log the exception, if it has not been done already.
138
139             tb = sys.exc_info()[2]
140             if tb is not None:
141                 try:
142
143                     # Obtain the inner-most traceback object.
144                     
145                     while tb.tb_next is not None:
146                         tb = tb.tb_next
147
148                     # If we have not logged a traceback yet, or this is a
149                     # different one than last time, log it.
150                     
151                     if cls._ExceptionTracebackID != id(tb):
152                         cls._ExceptionTracebackID = id(tb)
153                         cls._ReportedSubsequentErrors = False
154                         logger.log(level, u'%s: %s', unicode(sys.exc_info()[0].__name__), unicode(sys.exc_info()[1]).rstrip())
155
156                         # Log useful debugging information, starting with a stack
157                         # trace. To log the stack trace, we could just call
158                         # logger.debug(exc_info=True), but that function ultimately
159                         # calls traceback.print_exception(), which only prints the
160                         # stack frames from the exception back to the frame that
161                         # handled the exception. It does NOT go all the way back up
162                         # the stack. That is is not good enough for us, because we
163                         # need to see how we got to the problem, all the way from
164                         # the program entry point. So we have to use the
165                         # traceback.extract_stack() function to get those outer
166                         # frames.
167
168                         logger.debug(_(u'---------- BEGINNING OF DEBUGGING INFORMATION ----------'))
169                         logger.debug(_(u'Traceback (most recent call last):'))
170                         stackTraceEntries = traceback.extract_stack(tb.tb_frame.f_back) + traceback.extract_tb(tb)
171                         for entry in traceback.format_list(stackTraceEntries):
172                             for line in entry.split('\n'):
173                                 if len(line) > 0:
174                                     logger.debug(line.rstrip())
175                         logger.debug(u'%s: %s', unicode(sys.exc_info()[0].__name__), unicode(sys.exc_info()[1]).rstrip())
176
177                         # Log the local and global variables of the most recent
178                         # GeoEco call, unless it is RaiseException.
179
180                         frame = tb.tb_frame
181                         try:
182                             while frame is not None and (frame.f_code.co_filename.find('GeoEco') == -1 or frame.f_code.co_filename.find('AssimilatedModules') >= 0 or frame.f_code.co_filename.endswith('Logging.py') and frame.f_code.co_name == 'RaiseException' or frame.f_code.co_filename.endswith('R.py') and frame.f_code.co_name in ['__call__', '__getitem__', '__setitem__', '__delitem__', '__getattr__', '__setattr__', '__delattr__']):
183                                 frame = frame.f_back
184                             if frame is None:
185                                 frame = tb.tb_frame
186                             logger.debug(_(u'Local variables for stack frame: File "%(file)s", line %(line)i, in %(func)s:') % {u'file' : frame.f_code.co_filename, u'line' : frame.f_lineno, u'func' : frame.f_code.co_name})
187                             keys = frame.f_locals.keys()
188                             keys.sort()
189                             for key in keys:
190                                 logger.debug(u'  %s = %s', unicode(key), repr(frame.f_locals[key]))
191
192                             logger.debug(_(u'Global variables for stack frame: File "%(file)s", line %(line)i, in %(func)s:') % {u'file' : frame.f_code.co_filename, u'line' : frame.f_lineno, u'func' : frame.f_code.co_name})
193                             keys = frame.f_globals.keys()
194                             keys.sort()
195                             for key in keys:
196                                 if key != '__builtins__':       # Don't bother dumping __builtins__
197                                     logger.debug(u'  %s = %s', unicode(key), repr(frame.f_globals[key]))
198                         finally:
199                             del frame            # Avoid memory cycle by explicitly deleting frame object; see Python documentation for discussion of this problem.
200
201                         # Log other useful info.                               
202
203                         logger.debug(_(u'Enviornment variables:'))
204                         keys =  os.environ.keys()
205                         keys.sort()
206                         for key in keys:
207                             logger.debug(u'  %s = %s', unicode(key), repr(os.environ[key]))
208
209                         logger.debug(_(u'Other variables:'))
210                         import GeoEco
211                         logger.debug(u'  GeoEco.__version__ = %s', repr(GeoEco.__version__))
212                         logger.debug(u'  sys.argv = %s', repr(sys.argv))
213                         logger.debug(u'  sys.version = %s', str(sys.version))
214                         logger.debug(u'  sys.version_info = %s', repr(sys.version_info))
215                         logger.debug(u'  sys.platform = %s', str(sys.platform))
216                         if isinstance(sys.platform, basestring) and sys.platform.lower() == 'win32':
217                             logger.debug(u'  sys.getwindowsversion() = %s', repr(sys.getwindowsversion()))
218                         logger.debug(u'  os.getcwd() = %s', str(os.getcwd()))
219                         logger.debug(u'  sys.path = %s', repr(sys.path))
220
221                         keys =  sys.modules.keys()
222                         keys.sort()
223                         logger.debug(_(u'Loaded modules: ') + u', '.join(keys))
224
225                         logger.debug(_(u'---------- END OF DEBUGGING INFORMATION ----------'))
226                 finally:
227                     del tb          # Avoid memory cycle by explicitly deleting traceback object; see Python documentation for discussion of this problem.
228             else:
229                 cls._ExceptionTracebackID = None
230
231             # If the caller provided a message, log it.
232
233             if format is not None:
234                
235                 # If this is the first message to be logged after the exception,
236                 # first log a message indicating that the following messages are
237                 # consequences of the original exception.
238
239                 if cls._ExceptionTracebackID is not None and not cls._ReportedSubsequentErrors:
240                     logger.log(level, _(u'The following consequences resulted from the original error:'))
241                     cls._ReportedSubsequentErrors = True
242
243                 # Log the message.
244                 
245                 logger.log(level, format.rstrip(), *args)
246            
247         except:
248             pass
249
250     @classmethod
251     def Initialize(cls, activateArcGISLogging=False, loggingConfigFile=None):
252         cls.__doc__.Obj.ValidateMethodInvocation()
253
254         # Hack: Set the _srcfile attribute in the logging module to
255         # None, so that the module does not try to walk the stack when
256         # emitting log messages. This stack walking screws up our
257         # exception logging code when we're running in the Python 2.5
258         # exception handler hosted in-process by ArcGIS 9.3.
259
260         logging._srcfile = None
261
262         # Hack: this module (GeoEco.Logging) defines a class _ArcGISLoggingHandler
263         # which derives from logging.Handler. We want the user to be able to
264         # reference this handler in a logging configuration file. But the logging.config
265         # module offers no way to import this module into its namespace, which
266         # prevents it from being able to instantiate our class. So we must manually
267         # import ourself into the logging namespace (! not logging.config !).
268
269         if not sys.modules[u'logging'].__dict__.has_key(u'GeoEco'):
270             sys.modules[u'logging'].__dict__[u'GeoEco'] = sys.modules[u'GeoEco']
271         if not sys.modules[u'logging'].__dict__.has_key(u'GeoEco.Logging'):
272             sys.modules[u'logging'].__dict__[u'GeoEco.Logging'] = sys.modules[u'GeoEco.Logging']
273
274         # If the caller provided a file, try to initialize the logging system
275         # from it.
276
277         callersFileWarning = None
278         if loggingConfigFile is not None:
279             callersFileWarning = cls._InitializeLoggingFromFile(loggingConfigFile)
280
281         # If the caller did not provide a file, or the logging system could not
282         # be initialized from it, try the user's default logging config file.
283
284         userDefaultFile = None
285         userDefaultFileWarning = None
286         if loggingConfigFile is None or callersFileWarning is not None:
287             if sys.platform.lower() == u'win32' and os.environ.has_key(u'APPDATA'):
288                 import codecs
289                 userDefaultFile = codecs.mbcs_decode(os.environ['APPDATA'])[0] + u'\\GeoEco\\Logging.ini'
290             if userDefaultFile is not None:
291                 if os.path.isfile(userDefaultFile):
292                     userDefaultFileWarning = cls._InitializeLoggingFromFile(userDefaultFile)
293                 else:
294                     userDefaultFile = None
295
296         # If if the caller's file or the user's default file did not work, try
297         # the system default logging file.
298
299         systemDefaultFile = os.path.join(os.path.dirname(sys.modules['GeoEco.Logging'].__file__), u'Configuration', u'Logging.ini')
300         systemDefaultFileWarning = None
301         if (loggingConfigFile is None or callersFileWarning is not None) and (userDefaultFile is None or userDefaultFileWarning is not None):
302             systemDefaultFileWarning = cls._InitializeLoggingFromFile(systemDefaultFile)
303
304         # If all of the above failed, initialize using a hard-coded
305         # configuration.
306
307         manualInitializationWarning = None       
308         if (loggingConfigFile is None or callersFileWarning is not None) and (userDefaultFile is None or userDefaultFileWarning is not None) and systemDefaultFileWarning is not None:
309             try:
310                 stdout_handler = logging.StreamHandler(strm=sys.stdout)
311                 stdout_handler.level = logging.INFO
312                 logging.getLogger(u'').addHandler(stdout_handler)
313                 logging.getLogger(u'').setLevel(logging.INFO)
314             except Exception, e:
315                 manualInitializationWarning = _(u'Failed to initialize the logging system from hard-coded settings. One of the logging functions reported the error: %s: %s') % (e.__class__.__name__, unicode(e))
316
317         # Log warning messages, if any, and a success message. We have to delay of
318         # all this until now, because until this point, the logging system may not
319         # have been initialized. If we were able to initialize it, we want any
320         # warning messages to be logged using the best possible settings.
321
322         if manualInitializationWarning is not None:
323             if callersFileWarning is not None:
324                 print(callersFileWarning)
325             if userDefaultFileWarning is not None:
326                 print(userDefaultFileWarning)
327             print(systemDefaultFileWarning)
328             print(manualInitializationWarning)
329             print(_(u'The logging system could not be initialized. Log messages will not be reported.'))
330
331         elif loggingConfigFile is not None and callersFileWarning is None:
332             cls.Debug(_(u'Logging system initialized from config file "%s".'), loggingConfigFile)
333
334         elif userDefaultFile is not None and userDefaultFileWarning is None:
335             if callersFileWarning is not None:
336                 cls.Warning(callersFileWarning)
337             cls.Debug(_(u'Logging system initialized from config file "%s".'), userDefaultFile)
338
339         elif systemDefaultFileWarning is None:
340             if callersFileWarning is not None:
341                 cls.Warning(callersFileWarning)
342             if userDefaultFileWarning is not None:
343                 cls.Warning(userDefaultFileWarning)
344             cls.Debug(_(u'Logging system initialized from config file "%s".'), systemDefaultFile)
345
346         elif manualInitializationWarning is None:
347             if callersFileWarning is not None:
348                 cls.Warning(callersFileWarning)
349             if userDefaultFileWarning is not None:
350                 cls.Warning(userDefaultFileWarning)
351             cls.Warning(systemDefaultFileWarning)
352             cls.Info(_(u'Log messages will only be sent to the console output (stdout).'))
353
354         # Active ArcGIS logging if requested.
355
356         if activateArcGISLogging and (loggingConfigFile is not None and callersFileWarning is None or userDefaultFile is not None and userDefaultFileWarning is None or systemDefaultFileWarning is None):
357             cls.ActivateArcGISLogging()
358
359     @classmethod
360     def ActivateArcGISLogging(cls):
361         _ArcGISLoggingHandler.Activate()
362
363     @classmethod
364     def _InitializeLoggingFromFile(cls, loggingConfigFile=None):
365         assert isinstance(loggingConfigFile, types.UnicodeType), u'loggingConfigFile must be a Unicode string'
366
367         # Try to open the file for reading, so we know it is accessible.
368
369         try:
370             f = file(loggingConfigFile, u'r')
371         except Exception, e:
372             return(_(u'Failed to initialize logging from the config file "%(file)s". The file could not be opened. The operating system reported: %(error)s: %(msg)s') % {u'file': loggingConfigFile, u'error': e.__class__.__name__, u'msg': unicode(e)})
373         try:
374             f.close()
375         except:
376             pass
377
378         # If that was successful, try to initialize logging.
379         #
380         # If the logging config file is improperly formatted, the logging
381         # initialization function can fail in two ways. First, it may raise an
382         # exception. In this case, catch it as usual and return a failure
383         # message.
384         #
385         # In the second failure mode, it simply prints an exception trace to
386         # stderr, but does not raise an exception or return a value indicating
387         # failure. This would be ok, except that it can leave the logging system
388         # in an inconsistent state: subsequent calls to logging functions such
389         # as logging.debug() will fail. Again, these failed calls swallow
390         # exceptions, just printing their traces to stderr.
391         #
392         # Because we don't want stderr to be spammed when logging fails to
393         # initialize, we detect the failure in the logging configuration
394         # function by capturing stderr, and return a failure message.
395
396         cap = _StderrCapturer()
397         cap.Start()
398         try:
399             try:
400                 # First handle a bug in logging.config.fileConfig that causes the
401                 # logging Handler class to raise an exception at shutdown. This bug
402                 # was reported in Aug 2006 and exists in Python 2.4.4 but is
403                 # supposedly fixed in Python 2.5.
404                 
405                 if globals().has_key(u'_TempHandler') and globals()[u'_TempHandler'] is not None:
406                     logging.getLogger(u'GeoEco').removeHandler(globals()[u'_TempHandler'])
407                     globals()[u'_TempHandler'].close()
408                     del globals()[u'_TempHandler']
409
410                 # Now read the config file.
411                 
412                 logging.config.fileConfig(loggingConfigFile)
413                
414             except Exception, e:
415                 return _(u'Failed to initialize logging from the config file "%(file)s". Please verify that the contents of the config file are valid. Search the Python documentation for "logging configuration file format". In Python 2.4, the article is titled "6.29.10.2 Configuration file format". Python\'s log configuration file parser (logging.config.fileConfig) reported: %(error)s: %(msg)s') % {u'file': loggingConfigFile, u'error': e.__class__.__name__, u'msg': unicode(e)}
416         finally:
417             result = cap.Stop()
418
419         if result is not None and len(result.strip()) > 0:
420             result_lines = result.strip().split(u'\n')
421             i = 1
422             while i < len(result_lines) and (len(result_lines[i]) == 0 or result_lines[i][0] == u' '):
423                 i = i + 1
424             message = u''
425             for j in range(i, len(result_lines)):
426                 message = message + result_lines[j] + u'\n'
427             message = message.strip()
428             return _(u'Failed to initialize logging from the config file "%s". Please verify that the contents of the config file are valid. Search the Python documentation for "logging configuration file format". In Python 2.4, the article is titled "6.29.10.2 Configuration file format". Python\'s log configuration file parser (logging.config.fileConfig) reported the error: %s') % (loggingConfigFile, message)
429
430         # Return successfully.
431
432         return None
433
434
435 class ProgressReporter(object):   
436     __doc__ = DynamicDocString()
437
438     def __init__(self,
439                  progressMessage1=_(u'Progress report: %(elapsed)s elapsed, %(opsCompleted)i operations completed, %(perOp)s per operation, %(opsRemaining)i remaining, estimated completion time: %(etc)s.'),
440                  progressMessage2=_(u'Progress report: %(elapsed)s elapsed, %(opsCompleted)i operations completed, %(perOp)s per operation.'),
441                  completionMessage=_(u'Processing complete: %(elapsed)s elapsed, %(opsCompleted)i operations completed, %(perOp)s per operation.'),
442                  abortedMessage=_(u'Processing stopped before all operations were completed: %(elapsed)s elapsed, %(opsCompleted)i operations completed, %(perOp)s per operation, %(opsIncomplete)i operations not completed.'),
443                  loggingChannel=u'GeoEco'):
444
445         self.ProgressMessage1 = progressMessage1
446         self.ProgressMessage2 = progressMessage2
447         self.CompletionMessage = completionMessage
448         self.AbortedMessage = abortedMessage
449         self.LoggingChannel = loggingChannel
450         self._TotalOperations = None
451         self._OperationsCompleted = 0
452         self._TimeStarted = None
453         self._TimeCompleted = None
454         self._ClockStarted = None
455         self._ClockNextReportTime = None
456
457     def _GetProgressMessage1(self):
458         return self._ProgressMessage1
459
460     def _SetProgressMessage1(self, value):
461         assert isinstance(value, (types.NoneType, types.UnicodeType)), u'ProgressMessage1 must be a Unicode string, or None.'
462         self._ProgressMessage1 = value
463    
464     ProgressMessage1 = property(_GetProgressMessage1, _SetProgressMessage1, doc=DynamicDocString())
465
466     def _GetProgressMessage2(self):
467         return self._ProgressMessage2
468
469     def _SetProgressMessage2(self, value):
470         assert isinstance(value, (types.NoneType, types.UnicodeType)), u'ProgressMessage2 must be a Unicode string, or None.'
471         self._ProgressMessage2 = value
472    
473     ProgressMessage2 = property(_GetProgressMessage2, _SetProgressMessage2, doc=DynamicDocString())
474
475     def _GetCompletionMessage(self):
476         return self._CompletionMessage
477
478     def _SetCompletionMessage(self, value):
479         assert isinstance(value, (types.NoneType, types.UnicodeType)), u'CompletionMessage must be a Unicode string, or None.'
480         self._CompletionMessage = value
481    
482     CompletionMessage = property(_GetCompletionMessage, _SetCompletionMessage, doc=DynamicDocString())
483
484     def _GetAbortedMessage(self):
485         return self._AbortedMessage
486
487     def _SetAbortedMessage(self, value):
488         assert isinstance(value, (types.NoneType, types.UnicodeType)), u'AbortedMessage must be a Unicode string, or None.'
489         self._AbortedMessage = value
490    
491     AbortedMessage = property(_GetAbortedMessage, _SetAbortedMessage, doc=DynamicDocString())
492
493     def _GetLoggingChannel(self):
494         return self._LoggingChannel
495
496     def _SetLoggingChannel(self, value):
497         assert isinstance(value, (types.NoneType, types.UnicodeType)), u'LoggingChannel must be a Unicode string, or None.'
498         self._LoggingChannel = value
499    
500     LoggingChannel = property(_GetLoggingChannel, _SetLoggingChannel, doc=DynamicDocString())
501
502     def _GetTotalOperations(self):
503         return self._TotalOperations
504
505     def _SetTotalOperations(self, value):
506         assert value is None or (isinstance(value, types.IntType) and value >= 0), u'totalOperations must be a non-negative integer, or None'
507         self._TotalOperations = value
508         if self._TotalOperations is not None and self.HasStarted and self._OperationsCompleted >= self._TotalOperations:
509             self.Stop()
510    
511     TotalOperations = property(_GetTotalOperations, _SetTotalOperations, doc=DynamicDocString())
512
513     def _GetOperationsCompleted(self):
514         return self._OperationsCompleted
515    
516     OperationsCompleted = property(_GetOperationsCompleted, doc=DynamicDocString())
517
518     def _GetTimeStarted(self):
519         return self._TimeStarted
520    
521     TimeStarted = property(_GetTimeStarted, doc=DynamicDocString())
522
523     def _GetTimeCompleted(self):
524         return self._TimeCompleted
525    
526     TimeCompleted = property(_GetTimeCompleted, doc=DynamicDocString())
527
528     def _GetHasStarted(self):
529         return self._TimeStarted is not None
530    
531     HasStarted = property(_GetHasStarted, doc=DynamicDocString())
532
533     def _GetHasCompleted(self):
534         return self._TimeCompleted is not None
535    
536     HasCompleted = property(_GetHasCompleted, doc=DynamicDocString())
537
538     def _GetTimeElapsed(self):
539         if self._TimeStarted is None:
540             return None
541         if self._TimeCompleted is None:
542             return datetime.timedelta(seconds = time.clock() - self._ClockStarted)
543         return self._TimeCompleted - self._TimeStarted
544
545     TimeElapsed = property(_GetTimeElapsed, doc=DynamicDocString())
546
547     def Start(self, totalOperations=None):
548         assert not self.HasStarted, u'This ProgressReporter was already started and cannot be started a second time'
549        
550         self.TotalOperations = totalOperations
551         self._OperationsCompleted = 0
552         self._TimeStarted = datetime.datetime.now()
553         self._ClockStarted = time.clock()
554         self._ClockNextReportTime = self._ClockStarted + 60.0
555
556         if totalOperations == 0:
557             self.Stop()
558
559     def ReportProgress(self, operationsCompleted=1):
560         assert self.HasStarted, u'This ProgressReporter has not been started'
561         assert operationsCompleted >= 1, u'operationsCompelted must be greater than or equal to 1'
562        
563         self._OperationsCompleted += operationsCompleted
564        
565         if self._TotalOperations is not None and self._OperationsCompleted >= self._TotalOperations:
566             self.Stop()
567             return
568        
569         clockNow = time.clock()
570         if clockNow >= self._ClockNextReportTime:
571             timeElapsed = self.TimeElapsed
572             timePerOp = timeElapsed / self._OperationsCompleted
573
574             if self._TotalOperations is not None:
575                 if self._ProgressMessage1 is not None:
576                     now = datetime.datetime.now()
577                     timeOfCompletion = now + timePerOp * (self._TotalOperations - self._OperationsCompleted)
578                     if now.day == timeOfCompletion.day:
579                         etc = unicode(timeOfCompletion.strftime('%X'))
580                     else:
581                         etc = unicode(timeOfCompletion.strftime('%c'))
582                     self._Log(self._FormatProgressMessage1(timeElapsed, self._OperationsCompleted, timePerOp, self._TotalOperations - self._OperationsCompleted, etc))
583
584             elif self._ProgressMessage2 is not None:
585                 self._Log(self._FormatProgressMessage2(timeElapsed, self._OperationsCompleted, timePerOp))
586
587             self._ClockNextReportTime = clockNow + 300.0
588
589     def Stop(self):
590         assert self.HasStarted, u'This ProgressReporter has not been started'
591         if self.HasCompleted:
592             return
593        
594         self._TimeCompleted = datetime.datetime.now()
595         timeElapsed = self.TimeElapsed
596         if self._OperationsCompleted > 0:
597             timePerOp = timeElapsed / self._OperationsCompleted
598         else:
599             timePerOp = datetime.timedelta()
600         if self._TotalOperations is None or self._OperationsCompleted >= self._TotalOperations:
601             if self._CompletionMessage is not None:
602                 self._Log(self._FormatCompletionMessage(timeElapsed, self._OperationsCompleted, timePerOp))
603         elif self._AbortedMessage is not None:
604             self._Log(self._FormatAbortedMessage(timeElapsed, self._OperationsCompleted, timePerOp, self._TotalOperations - self._OperationsCompleted))
605
606     def _Log(self, message):
607         try:
608             if self._LoggingChannel == u'GeoEco':
609                 Logger.Info(message)
610             else:
611                 logging.getLogger(self._LoggingChannel).info(message)
612         except:
613             pass
614
615     # Private methods intended to be overridden by derived classes
616     # that need to do custom formatting of the progress messages.
617
618     def _FormatProgressMessage1(self, timeElapsed, opsCompleted, timePerOp, opsRemaining, estimatedTimeOfCompletionString):
619         return self._ProgressMessage1 % {u'elapsed' : unicode(datetime.timedelta(days=timeElapsed.days, seconds=timeElapsed.seconds)), u'opsCompleted': opsCompleted, u'perOp': unicode(timePerOp), u'opsRemaining': opsRemaining, u'etc': estimatedTimeOfCompletionString}
620
621     def _FormatProgressMessage2(self, timeElapsed, opsCompleted, timePerOp):
622         return self._ProgressMessage2 % {u'elapsed' : unicode(datetime.timedelta(days=timeElapsed.days, seconds=timeElapsed.seconds)), u'opsCompleted': opsCompleted, u'perOp': unicode(timePerOp)}
623
624     def _FormatCompletionMessage(self, timeElapsed, opsCompleted, timePerOp):
625         return self._CompletionMessage % {u'elapsed' : unicode(datetime.timedelta(days=timeElapsed.days, seconds=timeElapsed.seconds)), u'opsCompleted': opsCompleted, u'perOp': unicode(timePerOp)}
626
627     def _FormatAbortedMessage(self, timeElapsed, opsCompleted, timePerOp, opsIncomplete):
628         return self._AbortedMessage % {u'elapsed' : unicode(datetime.timedelta(days=timeElapsed.days, seconds=timeElapsed.seconds)), u'opsCompleted': opsCompleted, u'perOp': unicode(timePerOp), u'opsIncomplete': opsIncomplete}
629
630
631 # Private classes and functions global to this module
632
633 class _ArcGISLoggingHandler(logging.Handler):
634
635     def __init__(self, level=logging.NOTSET):
636         logging.Handler.__init__(self, level)
637
638     def emit(self, record):
639         if self.__class__._Instance != self:
640             self.__class__._Instance = self
641         if not isinstance(record, logging.LogRecord):
642             return
643         try:
644             if self.__class__._PreactivationQueue is not None:
645                 if len(self.__class__._PreactivationQueue) == 1000:
646                     del self.__class__._PreactivationQueue[0]
647                 self.__class__._PreactivationQueue.append(record)
648             else:
649                 self._Emit(record)
650         except:
651             pass
652
653     def _Emit(self, record):
654         try:
655             from GeoEco.ArcGIS import GeoprocessorManager
656             message = self.format(record)
657             if GeoprocessorManager.GetGeoprocessorIsCOMObject():
658                 message = UserPreferredEncodingToUnicode(message)
659             else:
660                 message = UnicodeToUserPreferredEncoding(message)
661             if record.levelno >= logging.ERROR:
662                 GeoprocessorManager.GetGeoprocessor().AddError(message)
663             elif record.levelno >= logging.WARNING:
664                 GeoprocessorManager.GetGeoprocessor().AddWarning(message)
665             else:
666                 GeoprocessorManager.GetGeoprocessor().AddMessage(message)
667         except:
668             pass
669
670     @classmethod
671     def GetInstance(cls):
672         return _ArcGISLoggingHandler._Instance
673
674     @classmethod
675     def Activate(cls):
676         if cls._PreactivationQueue is not None:
677             from GeoEco.ArcGIS import GeoprocessorManager
678             if GeoprocessorManager.GetGeoprocessor() is None:
679                 GeoprocessorManager.InitializeGeoprocessor()
680             if GeoprocessorManager.GetArcGISMajorVersion() == 9 and GeoprocessorManager.GetArcGISMinorVersion() == 3 and GeoprocessorManager.GetArcGISServicePack() == 0:       # Handle ArcGIS 9.3 bug NIM036130 - Print statements inside an in-proc script tool will lead to a "<type 'exceptions.IOError'>: [Errno 9] Bad file descriptor" error
681                 _GeoEcoStreamHandler.Deactivate()
682             while len(cls._PreactivationQueue) > 0:
683                 record = cls._PreactivationQueue.pop(0)
684                 cls._Instance._Emit(record)
685                 del record
686             cls._PreactivationQueue = None
687
688     _Instance = None
689     _PreactivationQueue = []
690
691
692 class _GeoEcoStreamHandler(logging.StreamHandler):
693
694     def __init__(self, strm=None):
695         logging.StreamHandler.__init__(self, strm)
696
697     def emit(self, record):
698         if not _GeoEcoStreamHandler._Deactivated:
699             logging.StreamHandler.emit(self, record)
700
701     @classmethod
702     def Deactivate(cls):
703         _GeoEcoStreamHandler._Deactivated = True
704
705     _Deactivated = False
706
707
708 class _StderrCapturer(object):
709
710     def __init__(self):
711         self._Buffer = None
712         self._OriginalStderr = None
713
714     def Start(self):
715         if self._OriginalStderr is None:
716             self._Buffer = None
717             self._OriginalStderr = sys.stderr
718             sys.stderr = self
719
720     def write(self, obj):
721         if self._OriginalStderr is not None:
722             if self._Buffer is None:
723                 self._Buffer = unicode(obj)
724             else:
725                 self._Buffer = self._Buffer + unicode(obj)
726
727     def Stop(self):
728         if self._OriginalStderr is not None:
729             sys.stderr = self._OriginalStderr
730             self._OriginalStderr = None
731             return self._Buffer
732         else:
733             return None
734
735
736 # Initialize, but do not activate, the ArcGIS logging handler. Until the handler
737 # is activated by an external caller (by calling GeoEco.Logging.ActivateArcGISLogging)
738 # the handler will just queue log messages in memory. Then, when it is
739 # activated, it will dump the queue to ArcGIS. This ensures that all log
740 # messages are reported to the ArcGIS UI when GeoEco is used from an Arc
741 # geoprocessing script, even those generated before the Activate call is made.
742
743 _TempHandler = None         # This is required to address in Python's logging.config.fileConfig function: it calls logging._handlers.clear() but does not also remove the handlers from logging._handlerList
744 try:
745     if _ArcGISLoggingHandler.GetInstance() is None:         # I'm fairly sure this will always return None, since nobody can instantiate _ArcGISLoggingHandler without importing the module first, causing the code below to execute first.
746         _TempHandler = _ArcGISLoggingHandler(logging.INFO)
747         _logger = logging.getLogger(u'GeoEco')
748         if _logger is not None:
749             _logger.addHandler(_TempHandler)
750         del _logger
751 except:
752     pass
753
754
755 ###############################################################################
756 # Metadata: module
757 ###############################################################################
758
759 from GeoEco.Metadata import *
760 from GeoEco.Types import *
761
762 AddModuleMetadata(shortDescription=_(u'Implements the Logger class, which other GeoEco classes use to report activity to the user.'))
763
764 ###############################################################################
765 # Metadata: Logger class
766 ###############################################################################
767
768 AddClassMetadata(Logger,
769     shortDescription=_(u'Provides methods for reporting messages to the user.'),
770     isExposedAsCOMServer=True,
771     comIID=u'{0DA39145-CDE6-48F2-AFD9-8EA6623A3121}',
772     comCLSID=u'{EE5ED6B5-55E7-4D65-B627-EF062B8332C4}')
773
774 # Public properties
775
776 AddPropertyMetadata(Logger.LogInfoAsDebug,
777     typeMetadata=BooleanTypeMetadata(),
778     shortDescription=_(u'If True, informational messages will be logged as debug messages.'),
779     longDescription=_(
780 u"""Informational messages describe major processing steps that may be
781 interesting to the user but do not require the user to take any
782 action. For example, a method that performs three major processing
783 tasks might report an informational message after each step is
784 finished.
785
786 Sometimes your method may repeatedly call another method to accomplish
787 some processing that you consider to be relatively unimportant. For
788 example, your method may copy a bunch of files, and you might want to
789 inform the user that you are performing the copying but do not want to
790 inform them about every file. In this situation, set this property to
791 True prior to entering the copy loop, and restore it to False when you
792 are done."""),
793     isExposedToPythonCallers=True,
794     isExposedByCOM=True)
795
796 AddPropertyMetadata(Logger.LogErrorsAsWarnings,
797     typeMetadata=BooleanTypeMetadata(),
798     shortDescription=_(u'If True, errors will be logged as warning messages.'),
799     longDescription=_(
800 u"""Error messages describe failures that halt processing and require the user
801 to fix a problem and restart processing. ArcGIS highlights error messages in red
802 in its user interface, and fails geoprocessing if even one error is reported.
803
804 Sometimes you don't want to halt processing when an unimportant operation fails.
805 For example, you may not care that a temporary file could not be deleted. In
806 that situation, set this property to True prior to calling File.Delete to delete
807 the file. If the deletion fails, the errors that occur will be logged as
808 warnings rather than errors, and processing will not be halted. After
809 File.Delete returns, set this property back to False, so that subsequent errors
810 will be logged as errors."""),
811     isExposedToPythonCallers=True,
812     isExposedByCOM=True)
813
814 # Public method: Debug
815
816 AddMethodMetadata(Logger.Debug,
817     shortDescription=_(u'Reports a debugging message to the user.'),
818     longDescription=_(
819 u"""Like the C printf function or the Python % operator, this method generates a
820 message string by merging in the optional arguments into the format string.
821
822 Debugging messages describe processing details that are usually not interesting
823 unless the user is diagnosing a problem. The default configuration of the
824 logging system causes debugging messages to be discarded."""),
825     isExposedToPythonCallers=True,
826     isExposedByCOM=True)
827
828 AddArgumentMetadata(Logger.Debug, u'cls',
829     typeMetadata=ClassOrClassInstanceTypeMetadata(cls=Logger),
830     description=_(u'%s class or an instance of it.') % Logger.__name__)
831
832 AddArgumentMetadata(Logger.Debug, u'format',
833     typeMetadata=UnicodeStringTypeMetadata(),
834     description=_(
835 u"""A printf-style format string. While Unicode strings are encouraged, this
836 method will also accept an 8-bit string for this parameter. For a complete
837 specification of the format of this string, look up "% formatting" in the Python
838 documentation."""))
839
840 AddArgumentMetadata(Logger.Debug, u'args',
841     typeMetadata=TupleTypeMetadata(elementType=AnyObjectTypeMetadata(canBeNone=True)),
842     description=_(u'Values to insert into the format string.'))
843
844 # Public method: Info
845
846 AddMethodMetadata(Logger.Info,
847     shortDescription=_(u'Reports an informational message to the user.'),
848     longDescription=_(
849 u"""Like the C printf function or the Python % operator, this method generates a
850 message string by merging in the optional arguments into the format string.
851
852 Informational messages describe major processing steps that may be interesting to
853 the user but do not require the user to take any action. For example, a method
854 that performs three major processing tasks might report an informational message
855 after each step is finished. Do not report too many informational messages or
856 you may overwhelm the user. Report processing details as debug messages."""),
857     isExposedToPythonCallers=True,
858     isExposedByCOM=True)
859
860 AddArgumentMetadata(Logger.Info, u'cls',
861     typeMetadata=ClassOrClassInstanceTypeMetadata(cls=Logger),
862     description=Logger.Debug.__doc__.Obj.Arguments[0].Description)
863
864 AddArgumentMetadata(Logger.Info, u'format',
865     typeMetadata=UnicodeStringTypeMetadata(),
866     description=Logger.Debug.__doc__.Obj.Arguments[1].Description)
867
868 AddArgumentMetadata(Logger.Info, u'args',
869     typeMetadata=TupleTypeMetadata(elementType=AnyObjectTypeMetadata(canBeNone=True)),
870     description=Logger.Debug.__doc__.Obj.Arguments[2].Description)
871
872 # Public method: Warning
873
874 AddMethodMetadata(Logger.Warning,
875     shortDescription=_(u'Reports a warning message to the user.'),
876     longDescription=_(
877 u"""Like the C printf function or the Python % operator, this method generates a
878 message string by merging in the optional arguments into the format string.
879
880 Warning messages describe important events that should be brought to the user's
881 attention but do not necessarily indicate that processing will fail. To draw the
882 user's attention, ArcGIS highlights warning messages in green in its user
883 interface.
884
885 *Note to GeoEco developers:* Do not call this method to report exceptions caught
886 inside methods of GeoEco classes. Call LogExceptionAsWarning instead."""),
887     isExposedToPythonCallers=True,
888     isExposedByCOM=True)
889
890 AddArgumentMetadata(Logger.Warning, u'cls',
891     typeMetadata=ClassOrClassInstanceTypeMetadata(cls=Logger),
892     description=Logger.Debug.__doc__.Obj.Arguments[0].Description)
893
894 AddArgumentMetadata(Logger.Warning, u'format',
895     typeMetadata=UnicodeStringTypeMetadata(),
896     description=Logger.Debug.__doc__.Obj.Arguments[1].Description)
897
898 AddArgumentMetadata(Logger.Warning, u'args',
899     typeMetadata=TupleTypeMetadata(elementType=AnyObjectTypeMetadata(canBeNone=True)),
900     description=Logger.Debug.__doc__.Obj.Arguments[2].Description)
901
902 # Public method: Error
903
904 AddMethodMetadata(Logger.Error,
905     shortDescription=_(u'Reports an error message to the user.'),
906     longDescription=_(
907 u"""Like the C printf function or the Python % operator, this method generates a
908 message string by merging in the optional arguments into the format string.
909
910 Error messages describe failures that halt processing and require the user to
911 fix a problem and restart processing. ArcGIS highlights error messages in red in
912 its user interface, and fails geoprocessing if even one error is reported.
913 Because of this, do not report error messages unless the problem is serious
914 enough to stop processing. For problems that may or may not be of consequence to
915 the user, report warning messages.
916
917 If LogWarningsAsErrors has been set to True, the message will be reported as a
918 warning rather than an error.
919
920 *Note to GeoEco developers:* Do not call this method to report exceptions caught
921 inside methods of GeoEco classes. Call LogExceptionAsError instead."""),
922     isExposedToPythonCallers=True,
923     isExposedByCOM=True)
924
925 AddArgumentMetadata(Logger.Error, u'cls',
926     typeMetadata=ClassOrClassInstanceTypeMetadata(cls=Logger),
927     description=Logger.Debug.__doc__.Obj.Arguments[0].Description)
928
929 AddArgumentMetadata(Logger.Error, u'format',
930     typeMetadata=UnicodeStringTypeMetadata(),
931     description=Logger.Debug.__doc__.Obj.Arguments[1].Description)
932
933 AddArgumentMetadata(Logger.Error, u'args',
934     typeMetadata=TupleTypeMetadata(elementType=AnyObjectTypeMetadata(canBeNone=True)),
935     description=Logger.Debug.__doc__.Obj.Arguments[2].Description)
936
937 # Public method: LogInfoAndSetInfoToDebug
938
939 AddMethodMetadata(Logger.LogInfoAndSetInfoToDebug,
940     shortDescription=_(u'Reports an informational message, sets the LogInfoAsDebug property to True, and returns its previous value.'),
941     longDescription=_(
942 u"""This function is used to efficiently implement a common logging
943 scenario: a function wants to log one informational message announcing
944 the processing it is doing and report everything else as debug
945 messages, including informational messages reported by nested
946 functions. The Python pattern for implementing this scenario is::
947
948     from GeoEco.Internationalization import _
949     from GeoEco.Logging import Logger
950
951     def MyFunc(...):
952         oldLogInfoAsDebug = Logger.LogInfoAndSetInfoToDebug(_(u'Doing my processing...'))
953         try:
954             ...
955         finally:
956             Logger.SetLogInfoAsDebug(oldLogInfoAsDebug)
957 """),
958     isExposedToPythonCallers=True,
959     isExposedByCOM=True)
960
961 CopyArgumentMetadata(Logger.Info, u'cls', Logger.LogInfoAndSetInfoToDebug, u'cls')
962 CopyArgumentMetadata(Logger.Info, u'format', Logger.LogInfoAndSetInfoToDebug, u'format')
963 CopyArgumentMetadata(Logger.Info, u'args', Logger.LogInfoAndSetInfoToDebug, u'args')
964
965 AddResultMetadata(Logger.LogInfoAndSetInfoToDebug, u'oldLogInfoAsDebug',
966     typeMetadata=BooleanTypeMetadata(),
967     description=_(u"""The old value of the LogInfoAsDebug property, prior to this method being invoked."""))
968
969 # Public method: RaiseException
970
971 AddMethodMetadata(Logger.RaiseException,
972     shortDescription=_(u'Raises a Python exception and logs it as an error message and additional information as debug messages.'),
973     longDescription=_(
974 u"""GeoEco classes should not raise exceptions by calling the Python raise
975 statement directly, but should call this method instead. This method will raise
976 the exception, log it as an error (or warning, if LogWarningsAsErrors has been
977 set to True), and also log a bunch of debugging information as debug messages."""))
978
979 AddArgumentMetadata(Logger.RaiseException, u'cls',
980     typeMetadata=ClassOrClassInstanceTypeMetadata(cls=Logger),
981     description=Logger.Debug.__doc__.Obj.Arguments[0].Description)
982
983 AddArgumentMetadata(Logger.RaiseException, u'exception',
984     typeMetadata=ClassInstanceTypeMetadata(cls=Exception),
985     description=_(u'The Python exception instance to raise.'))
986
987 # Public method: LogExceptionAsWarning
988
989 AddMethodMetadata(Logger.LogExceptionAsWarning,
990     shortDescription=_(u'Logs a Python exception caught by a GeoEco class as a warning message and additional information as debug messages.'),
991     longDescription=_(
992 u"""GeoEco classes should use LogExceptionAsWarning or LogExceptionAsError to
993 report exceptions caught by except clauses of try statements, like this::
994
995     Logger.Debug(_(u'Copying file %s to %s.') % (sourceFile, destinationFile))
996     try:
997         shutil.copy2(sourceFile, destinationFile)
998     except:
999         Logger.LogExceptionAsError(_(u'Could not copy file %(source)s to %(dest)s.') % \\
1000                                    {u'source' :  sourceFile, u'dest' : destinationFile})
1001         raise
1002
1003 As shown, the except clause should re-raise the exception (if appropriate) using
1004 a raise statement with no parameters. LogExceptionAsWarning and
1005 LogExceptionAsError will log the exception and some debugging information,
1006 including a stack trace. If the caller provides the optional format string, it
1007 is logged as a "consequence" of the original error. For example the code above
1008 produces the following output when the caller does not have permission to write
1009 the destination file::
1010
1011     DEBUG Copying file c:\\foo.txt to c:\\bar.txt.
1012     ERROR IOError: [Errno 13] Permission denied: u'c:\\\\bar.txt'
1013     DEBUG ---------- BEGINNING OF DEBUGGING INFORMATION ----------
1014     DEBUG Traceback (most recent call last):
1015     DEBUG   File "<stdin>", line 1, in ?
1016     DEBUG   File "<stdin>", line 2, in tryit
1017     DEBUG   File "C:\\Python24\\Lib\\site-packages\\GeoEco\\FileSystemUtils.py", line 47, in CopyFile
1018     DEBUG     shutil.copy2(sourceFile, destinationFile)
1019     DEBUG   File "C:\\Python24\\lib\\shutil.py", line 92, in copy2
1020     DEBUG     copyfile(src, dst)
1021     DEBUG   File "C:\\Python24\\lib\\shutil.py", line 48, in copyfile
1022     DEBUG     fdst = open(dst, 'wb')
1023     DEBUG IOError: [Errno 13] Permission denied: u'c:\\\\bar.txt'
1024     DEBUG End of traceback. Logging other useful debugging information...
1025     DEBUG sys.argv = ['']
1026     DEBUG sys.version = 2.4.4 (#71, Oct 18 2006, 08:34:43) [MSC v.1310 32 bit (Intel)]
1027     DEBUG sys.version_info = (2, 4, 4, 'final', 0)
1028     DEBUG sys.platform = win32
1029     DEBUG sys.getwindowsversion() = (5, 1, 2600, 2, 'Service Pack 2')
1030     DEBUG ...
1031     DEBUG ---------- END OF DEBUGGING INFORMATION ----------
1032     ERROR The following consequences resulted from the original error:
1033     ERROR Could not copy file c:\\foo.txt to c:\\bar.txt.
1034
1035 Unless the user has debugging messages turned on, they will only see the warning
1036 and error messages in the log::
1037
1038     ERROR IOError: [Errno 13] Permission denied: u'c:\\\\bar.txt'
1039     ERROR The following consequences resulted from the original error:
1040     ERROR Could not copy file c:\\foo.txt to c:\\bar2\\bar.txt.
1041
1042 Except clauses higher on the stack can also call LogExceptionAsWarning and
1043 LogExceptionAsError. The methods keep track of whether the original exception
1044 was logged and will not log it a second time. Instead they will just log the
1045 optional format string, if provided, as a subsequent "consequence" of the
1046 original exception. This allows nested methods to illustrate how the low-level
1047 failure causes a problem in the high-level operation the user actually cares
1048 about. For example, if the output file from a function cannot be copied from a
1049 temporary location because a directory cannot be created, the log might look
1050 like this::
1051
1052     ERROR IOError: [Errno 13] Permission denied: u'c:\\\\output'
1053     ERROR The following consequences resulted from the original error:
1054     ERROR Could create directory c:\\output.
1055     ERROR Could not copy file c:\\processing\\results.txt to c:\\output\\results.txt.
1056     ERROR Could not copy the results to the output directory."""))
1057
1058 AddArgumentMetadata(Logger.LogExceptionAsWarning, u'cls',
1059     typeMetadata=ClassOrClassInstanceTypeMetadata(cls=Logger),
1060     description=Logger.Debug.__doc__.Obj.Arguments[0].Description)
1061
1062 AddArgumentMetadata(Logger.LogExceptionAsWarning, u'format',
1063     typeMetadata=UnicodeStringTypeMetadata(canBeNone=True),
1064     description=Logger.Debug.__doc__.Obj.Arguments[1].Description)
1065
1066 AddArgumentMetadata(Logger.LogExceptionAsWarning, u'args',
1067     typeMetadata=TupleTypeMetadata(elementType=AnyObjectTypeMetadata(canBeNone=True)),
1068     description=Logger.Debug.__doc__.Obj.Arguments[2].Description)
1069
1070 # Public method: LogExceptionAsError
1071
1072 AddMethodMetadata(Logger.LogExceptionAsError,
1073     shortDescription=_(u'Logs a Python exception caught by a GeoEco class as an error message and additional information as debug messages.'),
1074     longDescription=Logger.LogExceptionAsWarning.__doc__.Obj.LongDescription)
1075
1076 AddArgumentMetadata(Logger.LogExceptionAsError, u'cls',
1077     typeMetadata=ClassOrClassInstanceTypeMetadata(cls=Logger),
1078     description=Logger.LogExceptionAsWarning.__doc__.Obj.Arguments[0].Description)
1079
1080 AddArgumentMetadata(Logger.LogExceptionAsError, u'format',
1081     typeMetadata=UnicodeStringTypeMetadata(canBeNone=True),
1082     description=Logger.LogExceptionAsWarning.__doc__.Obj.Arguments[1].Description)
1083
1084 AddArgumentMetadata(Logger.LogExceptionAsError, u'args',
1085     typeMetadata=TupleTypeMetadata(elementType=AnyObjectTypeMetadata(canBeNone=True)),
1086     description=Logger.LogExceptionAsWarning.__doc__.Obj.Arguments[2].Description)
1087
1088 # Public method: Initialize
1089
1090 AddMethodMetadata(Logger.Initialize,
1091     shortDescription=_(u'Initializes the logging system.'),
1092     isExposedToPythonCallers=True,
1093     isExposedByCOM=True)
1094
1095 AddArgumentMetadata(Logger.Initialize, u'cls',
1096     typeMetadata=ClassOrClassInstanceTypeMetadata(cls=Logger),
1097     description=Logger.Debug.__doc__.Obj.Arguments[0].Description)
1098
1099 AddArgumentMetadata(Logger.Initialize, u'activateArcGISLogging',
1100     typeMetadata=BooleanTypeMetadata(),
1101     description=_(
1102 u"""If true, logging messages will be delivered to the ArcGIS geoprocessing
1103 system and appear in the ArcGIS user interface. If false, they will be reported
1104 to other logging destinations but will be queued in memory for ArcGIS until the
1105 ActivateArcGISLogging method is called. To limit memory consumption in the event
1106 that the method is never called, the queue retains only the most recent 1000
1107 messages. If you are calling Initialize but not running as part of an ArcGIS
1108 geoprocessing *do not* pass true for this parameter. Passing true will decrease
1109 performance by initializing the ArcGIS geoprocessor when it might not be
1110 necessary to do so. (The memory used by the queue is negligible.)"""))
1111
1112 AddArgumentMetadata(Logger.Initialize, u'loggingConfigFile',
1113     typeMetadata=FileTypeMetadata(canBeNone=True, mustExist=True),
1114     description=_(
1115 u"""Path to the logging configuration file. If not provided, this
1116 function attempts to load a configuration file from these two
1117 locations:
1118
1119 * The file %APPDATA%\\\\GeoEco\\\\Logging.ini (e.g.
1120   C:\\\\Documents and Settings\\\\Jason\\\\Application Data\\\\GeoEco\\\\Logging.ini).
1121   This allows each user to configure their own default logging settings.
1122
1123 * The file Logging.ini in the Configuration subdirectory of the GeoEco
1124   Python package installation directory (e.g.
1125   C:\\\\Python24\\\\lib\\\\site-packages\\\\GeoEco\\\\Configuration\\\\Logging.ini).
1126   These settings will be used if the user does not specify their own
1127   Logging.ini file.
1128
1129 If neither is found (which would only occur if the GeoEco package
1130 installation is corrupt), then all INFO, WARNING, and ERROR messages
1131 will be logged to the console (i.e. the stdout stream)."""))
1132
1133 ###############################################################################
1134 # Metadata: ProgressReporter class
1135 ###############################################################################
1136
1137 AddClassMetadata(ProgressReporter,
1138     shortDescription=_(u'Provides a simple mechanism for long-running operations to report periodic progress to the user.'),
1139     longDescription=_(
1140 u"""This class provides progress-reporting capability for two kinds of
1141 long-running operations: those for which the total number of
1142 sub-operations is known when the long-running operation is started,
1143 and those for which the total number of sub-operations cannot be
1144 determined. In both cases, this class periodically reports the elapsed
1145 time, the number of completed sub-operations, and the average time per
1146 sub-operation. If the number of sub-operations is known, this class
1147 also reports the number of sub-operations remaining and the estimated
1148 time of completion.
1149
1150 When the total number of sub-operations is known, use this class like
1151 this::
1152
1153     operations = [...]                          # List of operations to perform
1154     progressReporter = ProgressReporter()
1155     progressReporter.Start(len(operations))
1156     for op in operations:
1157         ...                                     # Do one operation
1158         progressReporter.ReportProgress()
1159
1160 When the total number of sub-operations is not known, use this class
1161 like this::
1162
1163     progressReporter = ProgressReporter()
1164     progressReporter.Start()
1165     while True:
1166         ...                                     # Do one operation or exit loop if done
1167         progressReporter.ReportProgress()
1168     progressReporter.Stop()
1169
1170 The ReportProgress method will report the first message after
1171 one minute and an additional message every five minutes thereafter. A
1172 message will also be reported when processing is complete, by
1173 ReportProgress in the first scenario and Stop in the second scenario.
1174 You can configure the format of the progress messages by setting the
1175 ProgressMessage1, ProgressMessage2, and CompletionMessage properties.
1176 All messages are reported at the Info logging level. You can configure
1177 the logging channel that is used to report the messages by setting the
1178 LoggingChannel property."""))
1179
1180 # Public properties
1181
1182 AddPropertyMetadata(ProgressReporter.ProgressMessage1,
1183     typeMetadata=UnicodeStringTypeMetadata(),
1184     shortDescription=_(u'printf-style format string for periodically reporting progress when the total number of sub-operations is known a priori.'),
1185     longDescription=_(
1186 u"""Your string must include all five format specifiers, as is done in
1187 this example::
1188
1189     u'Progress report: %(elapsed)s elapsed, %(opsCompleted)i operations completed, %(perOp)s per operation, %(opsRemaining)i remaining, estimated completion time: %(etc)s.'
1190 """),
1191     isExposedToPythonCallers=True)
1192
1193 AddPropertyMetadata(ProgressReporter.ProgressMessage2,
1194     typeMetadata=UnicodeStringTypeMetadata(),
1195     shortDescription=_(u'printf-style format string for periodically reporting progress when the total number of sub-operations is not known a priori.'),
1196     longDescription=_(
1197 u"""Your string must include all three format specifiers, as is done
1198 in this example::
1199
1200     u'Progress report: %(elapsed)s elapsed, %(opsCompleted)i operations completed, %(perOp)s per operation.'
1201 """),
1202     isExposedToPythonCallers=True)
1203
1204 AddPropertyMetadata(ProgressReporter.CompletionMessage,
1205     typeMetadata=UnicodeStringTypeMetadata(),
1206     shortDescription=_(u'printf-style format string for reporting that processing is complete.'),
1207     longDescription=_(
1208 u"""Your string must include all three format specifiers, as is done
1209 in this example::
1210
1211     u'Processing complete: %(elapsed)s elapsed, %(opsCompleted)i operations completed, %(perOp)s per operation.'
1212 """),
1213     isExposedToPythonCallers=True)
1214
1215 AddPropertyMetadata(ProgressReporter.AbortedMessage,
1216     typeMetadata=UnicodeStringTypeMetadata(),
1217     shortDescription=_(u'printf-style format string for reporting that processing was stopped prematurely (i.e. that the Stop method was called before OperationsCompleted == TotalOperations).'),
1218     longDescription=_(
1219 u"""Your string must include all four format specifiers, as is done in
1220 this example::
1221
1222     u'Processing stopped before all operations were completed: %(elapsed)s elapsed, %(opsCompleted)i operations completed, %(perOp)s per operation, %(opsIncomplete)i operations not completed.'
1223 """),
1224     isExposedToPythonCallers=True)
1225
1226 AddPropertyMetadata(ProgressReporter.LoggingChannel,
1227     typeMetadata=UnicodeStringTypeMetadata(),
1228     shortDescription=_(u'Logging channel that progress messages should be reported to.'),
1229     longDescription=_(
1230 u"""Please see the documentation for the Python logging module formore
1231 information about logging channels. All progress messages are reported
1232 at the Info logging level. You can configure the message filtering and
1233 destinations by editing the GeoEco Logging.ini file."""),
1234     isExposedToPythonCallers=True)
1235
1236 AddPropertyMetadata(ProgressReporter.TotalOperations,
1237     typeMetadata=IntegerTypeMetadata(canBeNone=True),
1238     shortDescription=_(u'Total number of sub-operations in this long-running operation, or None if the number of sub-operations is not known a priori.'),
1239     longDescription=_(u'This property is initialized by the Start method.'),
1240     isExposedToPythonCallers=True)
1241
1242 AddPropertyMetadata(ProgressReporter.OperationsCompleted,
1243     typeMetadata=IntegerTypeMetadata(),
1244     shortDescription=_(u'Total number of sub-operations that have been completed so far.'),
1245     longDescription=_(u'This property is updated when ReportProgress is called.'),
1246     isExposedToPythonCallers=True)
1247
1248 AddPropertyMetadata(ProgressReporter.TimeStarted,
1249     typeMetadata=ClassInstanceTypeMetadata(cls=datetime.datetime, canBeNone=True),
1250     shortDescription=_(u'The time processing was started.'),
1251     longDescription=_(
1252 u"""This property is set to the current system time when Start is
1253 called, using the datetime.datetime.now method."""),
1254     isExposedToPythonCallers=True)
1255
1256 AddPropertyMetadata(ProgressReporter.TimeCompleted,
1257     typeMetadata=ClassInstanceTypeMetadata(cls=datetime.datetime, canBeNone=True),
1258     shortDescription=_(u'The time processing was completed.'),
1259     longDescription=_(
1260 u"""This property is set to the current system time when
1261 ReportProgress is called for the last sub-operation (when the total
1262 number of sub-operations is known a priori) or when Stop is called
1263 (when the the total number of sub-operations is not known a priori),
1264 using the datetime.datetime.now method."""),
1265     isExposedToPythonCallers=True)
1266
1267 AddPropertyMetadata(ProgressReporter.HasStarted,
1268     typeMetadata=BooleanTypeMetadata(),
1269     shortDescription=_(u'True if processing has started (i.e. if Start was called).'),
1270     isExposedToPythonCallers=True)
1271
1272 AddPropertyMetadata(ProgressReporter.HasCompleted,
1273     typeMetadata=BooleanTypeMetadata(),
1274     shortDescription=_(u'True if processing has completed (i.e. the last operation was reported to ReportProgress or Stop was called).'),
1275     isExposedToPythonCallers=True)
1276
1277 AddPropertyMetadata(ProgressReporter.TimeElapsed,
1278     typeMetadata=ClassInstanceTypeMetadata(cls=datetime.timedelta, canBeNone=True),
1279     shortDescription=_(u'The time elapsed since processing was started, or None if processing has not started yet.'),
1280     longDescription=_(
1281 u"""Processing starts when Start is called and stops when
1282 ReportProgress is called for the last sub-operation (when the total
1283 number of sub-operations is known a priori) or when Stop is called
1284 (when the the total number of sub-operations is not known a priori).
1285 After processing has stopped, this property will consistently return
1286 the total time required for processing (it will not keep increasing as
1287 additional time passes)."""),
1288     isExposedToPythonCallers=True)
1289
1290 # Constructor
1291
1292 AddMethodMetadata(ProgressReporter.__init__,
1293     shortDescription=_(u'Constructs a new %s instance.') % ProgressReporter.__name__,
1294     isExposedToPythonCallers=True)
1295
1296 AddArgumentMetadata(ProgressReporter.__init__, u'self',
1297     typeMetadata=ClassInstanceTypeMetadata(cls=ProgressReporter),
1298     description=_(u'%s instance.') % ProgressReporter.__name__)
1299
1300 AddArgumentMetadata(ProgressReporter.__init__, u'progressMessage1',
1301     typeMetadata=ProgressReporter.ProgressMessage1.__doc__.Obj.Type,
1302     description=ProgressReporter.ProgressMessage1.__doc__.Obj.ShortDescription + u'\n\n' + ProgressReporter.ProgressMessage1.__doc__.Obj.LongDescription)
1303
1304 AddArgumentMetadata(ProgressReporter.__init__, u'progressMessage2',
1305     typeMetadata=ProgressReporter.ProgressMessage2.__doc__.Obj.Type,
1306     description=ProgressReporter.ProgressMessage2.__doc__.Obj.ShortDescription + u'\n\n' + ProgressReporter.ProgressMessage2.__doc__.Obj.LongDescription)
1307
1308 AddArgumentMetadata(ProgressReporter.__init__, u'completionMessage',
1309     typeMetadata=ProgressReporter.CompletionMessage.__doc__.Obj.Type,
1310     description=ProgressReporter.CompletionMessage.__doc__.Obj.ShortDescription + u'\n\n' + ProgressReporter.CompletionMessage.__doc__.Obj.LongDescription)
1311
1312 AddArgumentMetadata(ProgressReporter.__init__, u'abortedMessage',
1313     typeMetadata=ProgressReporter.AbortedMessage.__doc__.Obj.Type,
1314     description=ProgressReporter.AbortedMessage.__doc__.Obj.ShortDescription + u'\n\n' + ProgressReporter.AbortedMessage.__doc__.Obj.LongDescription)
1315
1316 AddArgumentMetadata(ProgressReporter.__init__, u'loggingChannel',
1317     typeMetadata=ProgressReporter.LoggingChannel.__doc__.Obj.Type,
1318     description=ProgressReporter.LoggingChannel.__doc__.Obj.ShortDescription + u'\n\n' + ProgressReporter.LoggingChannel.__doc__.Obj.LongDescription)
1319
1320 AddResultMetadata(ProgressReporter.__init__, u'progressReporter',
1321     typeMetadata=ClassInstanceTypeMetadata(cls=ProgressReporter),
1322     description=_(u'New %s instance.') % ProgressReporter.__name__)
1323
1324 # Public method: Start
1325
1326 AddMethodMetadata(ProgressReporter.Start,
1327     shortDescription=_(u'Signals the ProgressReporter that processing has started.'),
1328     isExposedToPythonCallers=True)
1329
1330 CopyArgumentMetadata(ProgressReporter.__init__, u'self', ProgressReporter.Start, u'self')
1331
1332 AddArgumentMetadata(ProgressReporter.Start, u'totalOperations',
1333     typeMetadata=IntegerTypeMetadata(canBeNone=True, minValue=1),
1334     description=_(u'Total number of sub-operations that will be performed, or None if the number of sub-operations is not known.'))
1335
1336 # Public method: ReportProgress
1337
1338 AddMethodMetadata(ProgressReporter.ReportProgress,
1339     shortDescription=_(u'Signals the ProgressReporter that another one or more sub-operations just completed.'),
1340     longDescription=_(
1341 u"""This method will periodically report progress messages as
1342 described in the class-level documentation. You must call Start before
1343 calling this method."""),
1344     isExposedToPythonCallers=True)
1345
1346 CopyArgumentMetadata(ProgressReporter.__init__, u'self', ProgressReporter.ReportProgress, u'self')
1347
1348 AddArgumentMetadata(ProgressReporter.ReportProgress, u'operationsCompleted',
1349     typeMetadata=IntegerTypeMetadata(minValue=1),
1350     description=_(
1351 u"""Total number of sub-operations that just completed, typically 1.
1352 You should provide the number of sub-operations that have completed
1353 since you last called this method. Do not pass in a cumulative total."""))
1354
1355 # Public method: Stop
1356
1357 AddMethodMetadata(ProgressReporter.Stop,
1358     shortDescription=_(u'Signals the ProgressReporter that processing is complete.'),
1359     longDescription=_(
1360 u"""This method will report a completion message as described in the
1361 class-level documentation. You must call Start before calling this
1362 method. If provided a value for totalOperations when you called Start,
1363 and the number of completed operations has not reached this total,
1364 Stop will report AbortedMessage."""),
1365     isExposedToPythonCallers=True)
1366
1367 CopyArgumentMetadata(ProgressReporter.__init__, u'self', ProgressReporter.Stop, u'self')
1368
1369
1370 ###############################################################################
1371 # Names exported by this module
1372 ###############################################################################
1373
1374 __all__ = ['Logger']
Note: See TracBrowser for help on using the browser.