Skip to content

Commit 6b71875

Browse files
Zaytsev Dmitriyqq
Zaytsev Dmitriy
authored and
qq
committed
email notifications
1 parent 2281f68 commit 6b71875

File tree

6 files changed

+154
-11
lines changed

6 files changed

+154
-11
lines changed

pyprind/__init__.py

+1
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

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

pyprind/prog_class.py

+68-7
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,25 @@
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+
from email.mime.text import MIMEText
19+
from .email_notification import AESCipher
20+
21+
22+
try:
23+
from StringIO import StringIO
24+
except ImportError:
25+
from io import StringIO
26+
27+
try:
28+
import configparser
29+
except ImportError:
30+
import ConfigParser as configparser
1831

1932
try:
2033
import psutil
@@ -25,7 +38,7 @@
2538

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

58114
def update(self, iterations=1, item_id=None, force_flush=False):
59115
"""
@@ -145,8 +201,9 @@ def _finish(self):
145201
self.last_progress -= 1 # to force a refreshed _print()
146202
self._print()
147203
if self.track:
148-
self._stream_out('\nTotal time elapsed: ' +
149-
self._get_time(self.total_time))
204+
message = '\nTotal time elapsed: ' + \
205+
self._get_time(self.total_time)
206+
self._stream_out(message)
150207
self._stream_out('\n')
151208
self.active = False
152209

@@ -191,8 +248,12 @@ def __repr__(self):
191248

192249
cpu_mem_info = ' CPU %: {:.2f}\n'\
193250
' Memory %: {:.2f}'.format(cpu_total, mem_total)
194-
195-
return time_info + '\n' + cpu_mem_info
251+
time_elapsed = '\nTotal time elapsed: ' + \
252+
self._get_time(self.total_time)
253+
body_message = time_info + '\n' + cpu_mem_info
254+
if self.config:
255+
self.send_email("{}\n{}".format(time_elapsed, body_message))
256+
return body_message
196257
else:
197258
return time_info
198259

pyprind/progbar.py

+3-2
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

+2-1
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

+4-1
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)