Skip to content

Commit 32ff9f2

Browse files
Zaytsev Dmitriyqq
authored andcommitted
email notifications
req
1 parent 2281f68 commit 32ff9f2

File tree

6 files changed

+139
-11
lines changed

6 files changed

+139
-11
lines changed

pyprind/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from .progpercent import ProgPercent
1616
from .generator_factory import prog_percent
1717
from .generator_factory import prog_bar
18+
from .email_notification import setup_email
1819

1920

2021
__version__ = '2.9.8'

pyprind/email_notification.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import base64
2+
import os
3+
import ConfigParser
4+
5+
from Crypto.Cipher import AES
6+
from Crypto import Random
7+
8+
9+
class AESCipher(object):
10+
11+
def __init__(self):
12+
self.dir_path = os.path.dirname(os.path.abspath(__file__))
13+
self.key = self.generate_key()
14+
self.file = None
15+
self.get_current_path()
16+
17+
@staticmethod
18+
def pad(s):
19+
return s + (16 - len(s) % 16) * chr(16 - len(s) % 16)
20+
21+
@staticmethod
22+
def unpad(s):
23+
return s[:-ord(s[len(s) - 1:])]
24+
25+
def get_current_path(self):
26+
self.file = os.path.join(self.dir_path, 'email_settings.ini.enc')
27+
28+
def generate_key(self):
29+
key_path = os.path.join(self.dir_path, 'pyprind.key')
30+
if not os.path.exists(key_path):
31+
with open(key_path, 'wb') as key_file:
32+
key_file.write(os.urandom(16))
33+
with open(key_path, 'rb') as f:
34+
key = f.read()
35+
return key
36+
37+
def encrypt(self, text):
38+
text = self.pad(text)
39+
iv = Random.new().read(AES.block_size)
40+
cipher = AES.new(self.key, AES.MODE_CBC, iv)
41+
encrypted_mes = base64.b64encode(iv + cipher.encrypt(text))
42+
with open(self.file, 'wb') as f:
43+
f.write(encrypted_mes)
44+
45+
def decrypt(self):
46+
with open(self.file, 'rb') as f:
47+
enc = base64.b64decode(f.read())
48+
iv = enc[:16]
49+
cipher = AES.new(self.key, AES.MODE_CBC, iv)
50+
return self.unpad(cipher.decrypt(enc[16:]))
51+
52+
53+
def setup_email(smtp_server, smtp_port, username, password):
54+
dir_path = os.path.dirname(os.path.abspath(__file__))
55+
file_path = os.path.join(dir_path, 'email_settings.ini.enc')
56+
cipher = AESCipher()
57+
config = ConfigParser.ConfigParser()
58+
config.add_section('Email')
59+
config.set('Email', 'smtp_server', smtp_server)
60+
config.set('Email', 'smtp_port', smtp_port)
61+
config.set('Email', 'username', username)
62+
config.set('Email', 'password', password)
63+
with open(file_path, 'wb') as f:
64+
config.write(f)
65+
with open(file_path, 'rb') as af:
66+
cipher.encrypt(af.read())

pyprind/prog_class.py

Lines changed: 63 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,20 @@
99
Code Repository: https://github.com/rasbt/pyprind
1010
PyPI: https://pypi.python.org/pypi/PyPrind
1111
"""
12-
13-
12+
import smtplib
13+
import socket
1414
import time
1515
import sys
1616
import os
1717
from io import UnsupportedOperation
18+
import ConfigParser
19+
from email.mime.text import MIMEText
20+
from email_notification import AESCipher
21+
22+
try:
23+
from StringIO import StringIO
24+
except ImportError:
25+
from io import StringIO
1826

1927
try:
2028
import psutil
@@ -25,7 +33,7 @@
2533

2634
class Prog():
2735
def __init__(self, iterations, track_time, stream, title,
28-
monitor, update_interval=None):
36+
monitor, update_interval=None, email=False):
2937
""" Initializes tracking object. """
3038
self.cnt = 0
3139
self.title = title
@@ -54,6 +62,49 @@ def __init__(self, iterations, track_time, stream, title,
5462
self.process = psutil.Process()
5563
if self.track:
5664
self.eta = 1
65+
self.config = self.load_email_config() if email else False
66+
67+
def load_email_config(self):
68+
dir_path = os.path.dirname(os.path.abspath(__file__))
69+
file_path = os.path.join(dir_path, 'email_settings.ini.enc')
70+
if not os.path.exists(file_path):
71+
print('The email config cannot be found, please call'
72+
' pyprind.setup_email function')
73+
return False
74+
return self.parse_email_config()
75+
76+
@staticmethod
77+
def parse_email_config():
78+
buf = StringIO.StringIO()
79+
cipher = AESCipher()
80+
raw_data = cipher.decrypt()
81+
buf.write(raw_data)
82+
buf.seek(0, os.SEEK_SET)
83+
config = ConfigParser.ConfigParser()
84+
config.readfp(buf)
85+
return config
86+
87+
def send_email(self, message):
88+
email_address = self.config.get('Email', 'username')
89+
msg = MIMEText(message, 'plain')
90+
msg['From'] = email_address
91+
msg['To'] = email_address
92+
msg['Subject'] = 'Your task has finished'
93+
password = self.config.get('Email', 'password')
94+
self.config.get('Email', 'smtp_port')
95+
s = smtplib.SMTP_SSL()
96+
s.connect(self.config.get('Email', 'smtp_server'),
97+
self.config.get('Email', 'smtp_port'))
98+
try:
99+
s.login(email_address, password)
100+
except smtplib.SMTPAuthenticationError as e:
101+
print('Error occurred while sending email: %s' % e)
102+
return False
103+
try:
104+
s.sendmail(email_address, [email_address], msg.as_string())
105+
s.quit()
106+
except socket.error as e:
107+
print('Error occurred while sending email: %s' % e)
57108

58109
def update(self, iterations=1, item_id=None, force_flush=False):
59110
"""
@@ -145,8 +196,9 @@ def _finish(self):
145196
self.last_progress -= 1 # to force a refreshed _print()
146197
self._print()
147198
if self.track:
148-
self._stream_out('\nTotal time elapsed: ' +
149-
self._get_time(self.total_time))
199+
message = '\nTotal time elapsed: ' + \
200+
self._get_time(self.total_time)
201+
self._stream_out(message)
150202
self._stream_out('\n')
151203
self.active = False
152204

@@ -191,8 +243,12 @@ def __repr__(self):
191243

192244
cpu_mem_info = ' CPU %: {:.2f}\n'\
193245
' Memory %: {:.2f}'.format(cpu_total, mem_total)
194-
195-
return time_info + '\n' + cpu_mem_info
246+
time_elapsed = '\nTotal time elapsed: ' + \
247+
self._get_time(self.total_time)
248+
body_message = time_info + '\n' + cpu_mem_info
249+
if self.config:
250+
self.send_email("{}\n{}".format(time_elapsed, body_message))
251+
return body_message
196252
else:
197253
return time_info
198254

pyprind/progbar.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,10 @@ class ProgBar(Prog):
4343
4444
"""
4545
def __init__(self, iterations, track_time=True, width=30, bar_char='#',
46-
stream=2, title='', monitor=False, update_interval=None):
46+
stream=2, title='', monitor=False, update_interval=None,
47+
email=True):
4748
Prog.__init__(self, iterations, track_time,
48-
stream, title, monitor, update_interval)
49+
stream, title, monitor, update_interval, email)
4950
self.bar_width = width
5051
self._adjust_width()
5152
self.bar_char = bar_char

requirements.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
psutil>=3.2.0
1+
psutil>=3.2.0
2+
pycryptodome==3.4

setup.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,12 @@
1212

1313

1414
from setuptools import setup, find_packages
15+
from pip.req import parse_requirements
1516
import pyprind
1617

1718
VERSION = pyprind.__version__
19+
install_requirements = parse_requirements('requirements.txt', session=False)
20+
requires = [str(i.req) for i in install_requirements]
1821

1922
setup(name='PyPrind',
2023
version=VERSION,
@@ -25,12 +28,12 @@
2528
packages=find_packages(),
2629
package_data={'': ['LICENSE',
2730
'README.md',
28-
'requirements.txt',
2931
'CHANGELOG.md',
3032
'CONTRIBUTING.md'],
3133
'tests': ['tests/test_percentage_indicator.py',
3234
'tests/test_progress_bar.py']},
3335
include_package_data=True,
36+
install_requires=requires,
3437
license='BSD 3-Clause',
3538
platforms='any',
3639
classifiers=[

0 commit comments

Comments
 (0)