1"""
2Sebastian Raschka 2014-2017
3Python Progress Indicator Utility
4
5Author: Sebastian Raschka <sebastianraschka.com>
6License: BSD 3 clause
7
8Contributors: https://github.com/rasbt/pyprind/graphs/contributors
9Code Repository: https://github.com/rasbt/pyprind
10PyPI: https://pypi.python.org/pypi/PyPrind
11"""
12
13
14import time
15import sys
16import os
17from io import UnsupportedOperation
18
19try:
20    import psutil
21    psutil_import = True
22except ImportError:
23    psutil_import = False
24
25
26class Prog():
27    def __init__(self, iterations, track_time, stream, title,
28                 monitor, update_interval=None):
29        """ Initializes tracking object. """
30        self.cnt = 0
31        self.title = title
32        self.max_iter = float(iterations)  # to support Python 2.x
33        self.track = track_time
34        self.start = time.time()
35        self.end = None
36        self.item_id = None
37        self.eta = None
38        self.total_time = 0.0
39        self.last_time = self.start
40        self.monitor = monitor
41        self.stream = stream
42        self.active = True
43        self._stream_out = self._no_stream
44        self._stream_flush = self._no_stream
45        self._check_stream()
46        self._print_title()
47        self.update_interval = update_interval
48        self._cached_output = ''
49
50        sys.stdout.flush()
51        sys.stderr.flush()
52
53        if monitor:
54            if not psutil_import:
55                raise ValueError('psutil package is required when using'
56                                 ' the `monitor` option.')
57            else:
58                self.process = psutil.Process()
59        if self.track:
60            self.eta = 1
61
62    def update(self, iterations=1, item_id=None, force_flush=False):
63        """
64        Updates the progress bar / percentage indicator.
65
66        Parameters
67        ----------
68        iterations : int (default: 1)
69            default argument can be changed to integer values
70            >=1 in order to update the progress indicators more than once
71            per iteration.
72        item_id : str (default: None)
73            Print an item_id sring behind the progress bar
74        force_flush : bool (default: False)
75            If True, flushes the progress indicator to the output screen
76            in each iteration.
77
78        """
79        self.item_id = item_id
80        self.cnt += iterations
81        self._print(force_flush=force_flush)
82        self._finish()
83
84    def stop(self):
85        """Stops the progress bar / percentage indicator if necessary."""
86        self.cnt = self.max_iter
87        self._finish()
88
89    def _check_stream(self):
90        """Determines which output stream (stdout, stderr, or custom) to use"""
91        if self.stream:
92
93            try:
94                supported = ('PYCHARM_HOSTED' in os.environ or
95                             os.isatty(sys.stdout.fileno()))
96
97            except AttributeError:
98                supported = False
99
100            # a fix for IPython notebook "IOStream has no fileno."
101            except(UnsupportedOperation):
102                supported = True
103
104            else:
105                if self.stream is not None and hasattr(self.stream, 'write'):
106                    self._stream_out = self.stream.write
107                    self._stream_flush = self.stream.flush
108
109            if supported:
110                if self.stream == 1:
111                    self._stream_out = sys.stdout.write
112                    self._stream_flush = sys.stdout.flush
113                elif self.stream == 2:
114                    self._stream_out = sys.stderr.write
115                    self._stream_flush = sys.stderr.flush
116            else:
117                if self.stream is not None and hasattr(self.stream, 'write'):
118                    self._stream_out = self.stream.write
119                    self._stream_flush = self.stream.flush
120                else:
121                    print('Warning: No valid output stream.')
122
123    def _elapsed(self):
124        """ Returns elapsed time at update. """
125        self.last_time = time.time()
126        return self.last_time - self.start
127
128    def _calc_eta(self):
129        """ Calculates estimated time left until completion. """
130        elapsed = self._elapsed()
131        if self.cnt == 0 or elapsed < 0.001:
132            return None
133        rate = float(self.cnt) / elapsed
134        self.eta = (float(self.max_iter) - float(self.cnt)) / rate
135
136    def _calc_percent(self):
137        """Calculates the rel. progress in percent with 2 decimal points."""
138        return round(self.cnt / self.max_iter * 100, 2)
139
140    def _no_stream(self, text=None):
141        """ Called when no valid output stream is available. """
142        pass
143
144    def _get_time(self, _time):
145        if (_time < 86400):
146            return time.strftime("%H:%M:%S", time.gmtime(_time))
147        else:
148            s = (str(int(_time // 3600)) + ':' +
149                 time.strftime("%M:%S", time.gmtime(_time)))
150            return s
151
152    def _finish(self):
153        """ Determines if maximum number of iterations (seed) is reached. """
154        if self.active and self.cnt >= self.max_iter:
155            self.total_time = self._elapsed()
156            self.end = time.time()
157            self.last_progress -= 1  # to force a refreshed _print()
158            self._print()
159            if self.track:
160                self._stream_out('\nTotal time elapsed: ' +
161                                 self._get_time(self.total_time))
162            self._stream_out('\n')
163            self.active = False
164
165    def _print_title(self):
166        """ Prints tracking title at initialization. """
167        if self.title:
168            self._stream_out('{}\n'.format(self.title))
169            self._stream_flush()
170
171    def _cache_eta(self):
172        """ Prints the estimated time left."""
173        self._calc_eta()
174        self._cached_output += ' | ETA: ' + self._get_time(self.eta)
175
176    def _cache_item_id(self):
177        """ Prints an item id behind the tracking object."""
178        self._cached_output += ' | Item ID: %s' % self.item_id
179
180    def __repr__(self):
181        str_start = time.strftime('%m/%d/%Y %H:%M:%S',
182                                  time.localtime(self.start))
183        str_end = time.strftime('%m/%d/%Y %H:%M:%S',
184                                time.localtime(self.end))
185        self._stream_flush()
186
187        time_info = 'Title: {}\n'\
188                    '  Started: {}\n'\
189                    '  Finished: {}\n'\
190                    '  Total time elapsed: '.format(self.title,
191                                                    str_start,
192                                                    str_end)\
193                    + self._get_time(self.total_time)
194        if self.monitor:
195            try:
196                cpu_total = self.process.cpu_percent()
197                mem_total = self.process.memory_percent()
198            except AttributeError:  # old version of psutil
199                cpu_total = self.process.get_cpu_percent()
200                mem_total = self.process.get_memory_percent()
201
202            cpu_mem_info = '  CPU %: {:.2f}\n'\
203                           '  Memory %: {:.2f}'.format(cpu_total, mem_total)
204
205            return time_info + '\n' + cpu_mem_info
206        else:
207            return time_info
208
209    def __str__(self):
210        return self.__repr__()
211