email-smtp 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 Ouroboros Coding Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,27 @@
1
+ Metadata-Version: 2.1
2
+ Name: email-smtp
3
+ Version: 1.0.0
4
+ Summary: UNKNOWN
5
+ Home-page: https://github.com/ouroboroscoding/email-smtp-python
6
+ Author: Chris Nasr - Ouroboros Coding Inc.
7
+ Author-email: chris@ouroboroscoding.com
8
+ License: MIT
9
+ Project-URL: Source, https://github.com/ouroboroscoding/email-smtp-python
10
+ Project-URL: Tracker, https://github.com/ouroboroscoding/email-smtp-python/issues
11
+ Keywords: json
12
+ Platform: UNKNOWN
13
+ Requires-Python: >=3.10
14
+ Description-Content-Type: text/markdown
15
+ License-File: LICENSE
16
+
17
+ # Email SMTP
18
+ Handles sending emails using SMTP
19
+
20
+ # Install
21
+ ```
22
+ pip install email-smtp
23
+ ```
24
+
25
+ # Requires
26
+ email-smtp requires python 3.10 or higher
27
+
@@ -0,0 +1,10 @@
1
+ # Email SMTP
2
+ Handles sending emails using SMTP
3
+
4
+ # Install
5
+ ```
6
+ pip install email-smtp
7
+ ```
8
+
9
+ # Requires
10
+ email-smtp requires python 3.10 or higher
@@ -0,0 +1,336 @@
1
+ # coding=utf8
2
+ """ Email
3
+
4
+ Wrapper for python smtp module
5
+ """
6
+
7
+ __author__ = "Chris Nasr"
8
+ __copyright__ = "Ouroboros Coding Inc."
9
+ __version__ = "1.0.0"
10
+ __email__ = "chris@ouroboroscoding.com"
11
+ __created__ = "2018-11-17"
12
+
13
+ # Ouroboros imports
14
+ from config import config
15
+ from tools import evaluate
16
+ import undefined
17
+
18
+ # Python imports
19
+ from base64 import b64decode
20
+ from email.mime.application import MIMEApplication
21
+ from email.mime.multipart import MIMEMultipart
22
+ from email.mime.text import MIMEText
23
+ from email.utils import formatdate
24
+ from os.path import basename
25
+ import platform
26
+ import re
27
+ import smtplib
28
+ import socket
29
+ from sys import stderr
30
+ from typing import List
31
+
32
+ # Init the local variables
33
+ __smtp = None
34
+
35
+ # Init the last error message
36
+ __error: str = ''
37
+
38
+ __regex = re.compile(r'^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}$')
39
+ """E-mail address regular expression"""
40
+
41
+ # Create the defines
42
+ OK: int = 0
43
+ EUNKNOWN: int = -1
44
+ ECONNECT: int = -2
45
+ ELOGIN: int = -3
46
+
47
+ def _addresses(l):
48
+ """Addresses
49
+
50
+ Takes a string or list of strings and returns them formatted for to:, cc:, \
51
+ or bcc:
52
+
53
+ Arguments:
54
+ l (str | str[]): The address or list of addresses
55
+
56
+ Returns:
57
+ str
58
+ """
59
+
60
+ # If we got a list, tuple, or set, join them, else return as is
61
+ if isinstance(l, (list,tuple,set)): return ', '.join(l)
62
+ else: return l
63
+
64
+ def _to(l):
65
+ """To
66
+
67
+ Converts all addresses passed, whether strings or lists, into one singular \
68
+ list
69
+
70
+ Arguments:
71
+ l (list(str | str[])): The list of addresses or lists of addresses
72
+
73
+ Returns:
74
+ list
75
+ """
76
+
77
+ # Init the return list
78
+ lRet = []
79
+
80
+ # Go through each item in the list
81
+ for m in l:
82
+
83
+ # If we got a list, extend our existing list with it
84
+ if isinstance(m, (list,tuple)):
85
+ lRet.extend(m)
86
+
87
+ # Else, we got one address, just append it to our existing list
88
+ else:
89
+ lRet.append(m)
90
+
91
+ # Return the full list
92
+ return lRet
93
+
94
+ def error(message, to = undefined):
95
+ """Email Error
96
+
97
+ Send out an email with an error message
98
+
99
+ Arguments:
100
+ message (str): The error to email
101
+ to (str | str[]): The people to email, defaults to config.email.error_to
102
+
103
+ Returns:
104
+ bool
105
+ """
106
+
107
+ global __error
108
+
109
+ # If there's no to
110
+ if to is undefined:
111
+
112
+ # Get the address or addresses to email errors to
113
+ to = config.email.error_to()
114
+
115
+ # If we have no error_to
116
+ if not to:
117
+ raise KeyError('error_to', 'missing from config.email')
118
+
119
+ # Send the email
120
+ iRes = send(to, '%s Error' % platform.node(), {
121
+ 'text': message
122
+ })
123
+ if iRes != OK:
124
+ print(
125
+ 'Failed to send email: %s (%d)' % (__error, iRes),
126
+ file = stderr
127
+ )
128
+ return False
129
+
130
+ # Return OK
131
+ return True
132
+
133
+ def last_error() -> str:
134
+ """Last Error
135
+
136
+ Returns the last error message if there is one
137
+
138
+ Returns:
139
+ str
140
+ """
141
+ global __error
142
+ return __error
143
+
144
+ def send(to: str | List[str], subject: str, opts: dict) -> int:
145
+ """Send
146
+
147
+ Sends an e-mail to one or many addresses based on a dictionary of options
148
+
149
+ Arguments:
150
+ to (str | str[]): The email or emails to send the email to
151
+ subject (str): The subject of the email
152
+ opts (dict): The options used to generate the email and any headers
153
+ 'html': str
154
+ 'text': str
155
+ 'from': str,
156
+ 'reply-to': str
157
+ 'cc': str | str[]
158
+ 'bcc': str | str[]
159
+ 'attachments': list(str | dict('body', 'filename')) \
160
+ If an attachment is a string, a local filename is \
161
+ assumed, else if we receive a dictionary, it should \
162
+ contain the filename of the file, and the raw body \
163
+ of the file
164
+ 'unsubscribe': str
165
+
166
+ Returns:
167
+ int
168
+ """
169
+
170
+ # Import the module vars
171
+ global __error, __smtp
172
+
173
+ # If we have no config
174
+ if not __smtp:
175
+ __smtp = config.email.smtp({
176
+ 'host': 'localhost',
177
+ 'port': 25,
178
+ 'tls': False
179
+ })
180
+
181
+ # Init the list of total "to"s
182
+ lTO = [to]
183
+
184
+ # If from is missing, create a generic one
185
+ if 'from' not in opts:
186
+ opts['from'] = 'noreply@%s' % socket.gethostname()
187
+
188
+ # Create a new Mime MultiPart message
189
+ oMMP = MIMEMultipart('mixed')
190
+ oMMP['From'] = opts['from']
191
+ oMMP['To'] = _addresses(to)
192
+ oMMP['Date'] = formatdate()
193
+ oMMP['Subject'] = subject
194
+
195
+ # If we have a reply-to
196
+ if 'reply-to' in opts:
197
+ oMMP['reply-to'] = opts['reply-to']
198
+
199
+ # If we have cc
200
+ if 'cc' in opts:
201
+ oMMP['Cc'] = _addresses(opts['cc'])
202
+ lTO.append(opts['cc'])
203
+
204
+ # If we have bcc
205
+ if 'bcc' in opts:
206
+ lTO.append(opts['bcc'])
207
+
208
+ # If we have an unsubscribe string
209
+ if 'unsubscribe' in opts:
210
+ oMMP.add_header('List-Unsubscribe', opts['unsubscribe'])
211
+
212
+ # Create the alternative part for the content
213
+ oAlternative = MIMEMultipart('alternative')
214
+
215
+ # Check that text or html body is set
216
+ if 'text' not in opts and 'html' not in opts:
217
+ raise ValueError('need one of "text" or "html" in em.send options')
218
+
219
+ # Attach the main message
220
+ if 'text' in opts and opts['text']:
221
+ oAlternative.attach(MIMEText(opts['text'], 'plain'))
222
+ if 'html' in opts and opts['html']:
223
+ oAlternative.attach(MIMEText(opts['html'], 'html'))
224
+
225
+ # Add the alternative section to the email
226
+ oMMP.attach(oAlternative)
227
+
228
+ # If there's any attachments
229
+ if 'attachments' in opts:
230
+
231
+ # Make sure it's a list
232
+ if not isinstance(opts['attachments'], (list,tuple)):
233
+ opts['attachments'] = [opts['attachments']]
234
+
235
+ # Loop through the attachments
236
+ for i in range(len(opts['attachments'])):
237
+
238
+ # If we got a string
239
+ if isinstance(opts['attachments'][i], str):
240
+
241
+ # Assume it's a file and open it
242
+ with open(opts['attachments'][i], 'rb') as rFile:
243
+ oMMP.attach(MIMEApplication(
244
+ rFile.read(),
245
+ Content_Disposition='attachment; filename="%s"' %
246
+ basename(opts['attachments'][i]),
247
+ Name=basename(opts['attachments'][i])
248
+ ))
249
+
250
+ # Else, if got a dictionary
251
+ elif isinstance(opts['attachments'][i], dict):
252
+
253
+ # If the fields are missing
254
+ try:
255
+ evaluate(opts['attachments'][i], ['body', 'filename'])
256
+ except ValueError as e:
257
+ raise ValueError(
258
+ [('attachments[%d].%s' % (i, s), 'missing') \
259
+ for s in e.args]
260
+ )
261
+
262
+ # Add it
263
+ oMMP.attach(MIMEApplication(
264
+ b64decode(opts['attachments'][i]['body']),
265
+ Content_Disposition='attachment; filename="%s"' %
266
+ opts['attachments'][i]['filename'],
267
+ Name=opts['attachments'][i]['filename']
268
+ ))
269
+
270
+ # Else, error
271
+ else:
272
+ raise ValueError(
273
+ 'attachments[%d]' % i, 'invalid type, must be str or dict'
274
+ )
275
+
276
+ # Generate the body
277
+ sBody = oMMP.as_string()
278
+
279
+ # Catch any Connect or Authenticate Errors
280
+ try:
281
+
282
+ # Create a new instance of the SMTP class
283
+ oSMTP = smtplib.SMTP(__smtp['host'], __smtp['port'])
284
+
285
+ # If we need TLS
286
+ if __smtp['tls']:
287
+
288
+ # Start TLS
289
+ oSMTP.starttls()
290
+
291
+ # If there's a username
292
+ if __smtp['user']:
293
+
294
+ # Log in with the given credentials
295
+ oSMTP.login(__smtp['user'], __smtp['passwd'])
296
+
297
+ # Try to send the message, then close the SMTP
298
+ oSMTP.sendmail(opts['from'], _to(lTO), sBody)
299
+ oSMTP.close()
300
+
301
+ # Return ok
302
+ return OK
303
+
304
+ # If there's a connection error
305
+ except smtplib.SMTPConnectError as e:
306
+ __error = str(e.args)
307
+ return ECONNECT
308
+
309
+ # If there's am authentication error
310
+ except smtplib.SMTPAuthenticationError as e:
311
+ __error = str(e.args)
312
+ return ELOGIN
313
+
314
+ # If there's any other error
315
+ except (smtplib.SMTPException, Exception) as e:
316
+ __error = str(e.args)
317
+ return EUNKNOWN
318
+
319
+ def valid(address: str) -> bool:
320
+ """Valid
321
+
322
+ Returns true if the email address is valid
323
+
324
+ Arguments:
325
+ address (str): The e-mail address to verify
326
+
327
+ Returns
328
+ bool
329
+ """
330
+
331
+ # If we get a match
332
+ if __regex.match(address):
333
+ return True
334
+
335
+ # No match
336
+ return False
@@ -0,0 +1 @@
1
+ print('I compile, but I don\'t necessarily run')
@@ -0,0 +1,27 @@
1
+ Metadata-Version: 2.1
2
+ Name: email-smtp
3
+ Version: 1.0.0
4
+ Summary: UNKNOWN
5
+ Home-page: https://github.com/ouroboroscoding/email-smtp-python
6
+ Author: Chris Nasr - Ouroboros Coding Inc.
7
+ Author-email: chris@ouroboroscoding.com
8
+ License: MIT
9
+ Project-URL: Source, https://github.com/ouroboroscoding/email-smtp-python
10
+ Project-URL: Tracker, https://github.com/ouroboroscoding/email-smtp-python/issues
11
+ Keywords: json
12
+ Platform: UNKNOWN
13
+ Requires-Python: >=3.10
14
+ Description-Content-Type: text/markdown
15
+ License-File: LICENSE
16
+
17
+ # Email SMTP
18
+ Handles sending emails using SMTP
19
+
20
+ # Install
21
+ ```
22
+ pip install email-smtp
23
+ ```
24
+
25
+ # Requires
26
+ email-smtp requires python 3.10 or higher
27
+
@@ -0,0 +1,12 @@
1
+ LICENSE
2
+ README.md
3
+ setup.cfg
4
+ setup.py
5
+ em/__init__.py
6
+ em/__main__.py
7
+ email_smtp.egg-info/PKG-INFO
8
+ email_smtp.egg-info/SOURCES.txt
9
+ email_smtp.egg-info/dependency_links.txt
10
+ email_smtp.egg-info/requires.txt
11
+ email_smtp.egg-info/top_level.txt
12
+ email_smtp.egg-info/zip-safe
@@ -0,0 +1,3 @@
1
+ config-oc<1.1,>=1.0.3
2
+ tools-oc<1.3,>=1.2.3
3
+ undefined-oc<1.1,>=1.0.0
@@ -0,0 +1,7 @@
1
+ [metadata]
2
+ description-file = README.md
3
+
4
+ [egg_info]
5
+ tag_build =
6
+ tag_date = 0
7
+
@@ -0,0 +1,29 @@
1
+ from setuptools import setup
2
+
3
+ with open('README.md', 'r') as oF:
4
+ long_description=oF.read()
5
+
6
+ setup(
7
+ name='email-smtp',
8
+ version='1.0.0',
9
+ description='',
10
+ long_description=long_description,
11
+ long_description_content_type='text/markdown',
12
+ url='https://github.com/ouroboroscoding/email-smtp-python',
13
+ project_urls={
14
+ 'Source': 'https://github.com/ouroboroscoding/email-smtp-python',
15
+ 'Tracker': 'https://github.com/ouroboroscoding/email-smtp-python/issues'
16
+ },
17
+ keywords=['json'],
18
+ author='Chris Nasr - Ouroboros Coding Inc.',
19
+ author_email='chris@ouroboroscoding.com',
20
+ license='MIT',
21
+ packages=['em'],
22
+ python_requires='>=3.10',
23
+ install_requires=[
24
+ 'config-oc>=1.0.3,<1.1',
25
+ 'tools-oc>=1.2.3,<1.3',
26
+ 'undefined-oc>=1.0.0,<1.1'
27
+ ],
28
+ zip_safe=True
29
+ )