reykit 1.0.0__py3-none-any.whl

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.
reykit/remail.py ADDED
@@ -0,0 +1,276 @@
1
+ # !/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+
4
+ """
5
+ @Time : 2022-12-05 14:10:19
6
+ @Author : Rey
7
+ @Contact : reyxbo@163.com
8
+ @Explain : Email methods.
9
+ """
10
+
11
+
12
+ from typing import Optional, Union
13
+ from io import BufferedIOBase
14
+ from smtplib import SMTP
15
+ from email.mime.multipart import MIMEMultipart
16
+ from email.mime.text import MIMEText
17
+ from email.mime.application import MIMEApplication
18
+
19
+ from .rdata import unique
20
+ from .rexception import throw
21
+ from .ros import FileBytes, get_file_bytes
22
+
23
+
24
+ __all__ = (
25
+ 'REmail',
26
+ )
27
+
28
+
29
+ class REmail(object):
30
+ """
31
+ Rey's `email` type.
32
+ """
33
+
34
+
35
+ def __init__(
36
+ self,
37
+ username: str,
38
+ password: str
39
+ ) -> None:
40
+ """
41
+ Build `email` attributes.
42
+
43
+ Parameters
44
+ ----------
45
+ username : Email username.
46
+ password : Email password.
47
+ """
48
+
49
+ # Get parameter.
50
+ host, port = self.get_server_address(username)
51
+
52
+ # Set attribute.
53
+ self.username = username
54
+ self.password = password
55
+ self.host = host
56
+ self.port = port
57
+ self.smtp = SMTP(host, port)
58
+
59
+
60
+ def get_server_address(
61
+ self,
62
+ email: str
63
+ ) -> tuple[str, str]:
64
+ """
65
+ Get server address of email.
66
+
67
+ Parameters
68
+ ----------
69
+ email : Email address.
70
+
71
+ Returns
72
+ -------
73
+ Server address.
74
+ """
75
+
76
+ # Get.
77
+ domain_name = email.split('@')[-1]
78
+ host = 'smtp.' + domain_name
79
+ port = 25
80
+
81
+ return host, port
82
+
83
+
84
+ def get_smtp(self) -> SMTP:
85
+ """
86
+ Get `SMTP` connection instance and login.
87
+
88
+ Returns
89
+ -------
90
+ Instance.
91
+ """
92
+
93
+ # Login.
94
+ response = self.smtp.login(self.username, self.password)
95
+ code = response[0]
96
+ if code != 235:
97
+ throw(ConnectionError, response)
98
+
99
+ return self.smtp
100
+
101
+
102
+ def create_email(
103
+ self,
104
+ title: Optional[str],
105
+ text: Optional[str],
106
+ attachment: dict[str, bytes],
107
+ show_from: Optional[str],
108
+ show_to: Optional[list[str]],
109
+ show_cc: Optional[list[str]]
110
+ ) -> str:
111
+ """
112
+ create email content.
113
+
114
+ Parameters
115
+ ----------
116
+ title : Email title.
117
+ text : Email text.
118
+ attachment : Email attachments dictionary.
119
+ - `Key`: File name.
120
+ - `Value`: File bytes data.
121
+ show_from : Show from email address.
122
+ show_to : Show to email addresses list.
123
+ show_cc : Show carbon copy email addresses list.
124
+ """
125
+
126
+ # Handle parameter.
127
+ if show_to.__class__ == list:
128
+ show_to = ','.join(show_to)
129
+ if show_cc.__class__ == list:
130
+ show_cc = ','.join(show_cc)
131
+
132
+ # Instance.
133
+ mimes = MIMEMultipart()
134
+
135
+ # Add.
136
+
137
+ ## Title.
138
+ if title is not None:
139
+ mimes['subject'] = title
140
+
141
+ ## Text.
142
+ if text is not None:
143
+ mime_text = MIMEText(text)
144
+ mimes.attach(mime_text)
145
+
146
+ ## Attachment.
147
+ for file_name, file_bytes in attachment.items():
148
+ mime_file = MIMEApplication(file_bytes)
149
+ mime_file.add_header('Content-Disposition', 'attachment', filename=file_name)
150
+ mimes.attach(mime_file)
151
+
152
+ ## Show from.
153
+ if show_from is not None:
154
+ mimes['from'] = show_from
155
+
156
+ ## Show to.
157
+ if show_to is not None:
158
+ mimes['to'] = show_to
159
+
160
+ ## Show cc.
161
+ if show_cc is not None:
162
+ mimes['cc'] = show_cc
163
+
164
+ # Create.
165
+ email = mimes.as_string()
166
+
167
+ return email
168
+
169
+
170
+ def send_email(
171
+ self,
172
+ to: Union[str, list[str]],
173
+ title: Optional[str] = None,
174
+ text: Optional[str] = None,
175
+ attachment: dict[str, FileBytes] = {},
176
+ cc: Optional[Union[str, list[str]]] = None,
177
+ show_from: Optional[str] = None,
178
+ show_to: Optional[Union[str, list[str]]] = None,
179
+ show_cc: Optional[Union[str, list[str]]] = None
180
+ ) -> None:
181
+ """
182
+ Send email.
183
+
184
+ Parameters
185
+ ----------
186
+ to : To email addresses.
187
+ - `str`: Email address, multiple comma interval.
188
+ - `list[str]`: Email addresses list.
189
+ title : Email title.
190
+ text : Email text.
191
+ attachment : Email attachments dictionary.
192
+ - `Key`: File name.
193
+ - `Value`: File bytes data source.
194
+ `bytes`: File bytes data.
195
+ `str`: File path.
196
+ `BufferedIOBase`: File bytes IO.
197
+ cc : Carbon copy email addresses.
198
+ - `str`: Email address, multiple comma interval.
199
+ - `list[str]`: Email addresses list.
200
+ show_from : Show from email address.
201
+ - `None`: Use attribute `self.username`.
202
+ - `str`: Email address.
203
+ show_to : Show to email addresses.
204
+ - `None`: Use parameter `to`.
205
+ - `str`: Email address, multiple comma interval.
206
+ - `list[str]`: Email addresses list.
207
+ show_cc : Show carbon copy email addresses.
208
+ - `None`: Use parameter `cc`.
209
+ - `str`: Email address, multiple comma interval.
210
+ - `list[str]`: Email addresses list.
211
+ """
212
+
213
+ # Handle parameter.
214
+
215
+ ## To.
216
+ if to.__class__ == str:
217
+ to = to.split(',')
218
+
219
+ ## Cc.
220
+ match cc:
221
+ case None:
222
+ cc = []
223
+ case str():
224
+ cc = cc.split(',')
225
+
226
+ ## Show from.
227
+ show_from = show_from or self.username
228
+
229
+ ## Show to.
230
+ show_to = show_to or to
231
+ if show_to.__class__ == str:
232
+ show_to = show_to.split(',')
233
+
234
+ ## Show cc.
235
+ show_cc = show_cc or cc
236
+ if show_cc.__class__ == str:
237
+ show_cc = show_cc.split(',')
238
+
239
+ ## Attachment.
240
+ for file_name, file_source in attachment.items():
241
+ file_bytes = get_file_bytes(file_source)
242
+ attachment[file_name] = file_bytes
243
+
244
+ # Create email.
245
+ email = self.create_email(
246
+ title,
247
+ text,
248
+ attachment,
249
+ show_from,
250
+ show_to,
251
+ show_cc
252
+ )
253
+
254
+ # Get SMTP.
255
+ smtp = self.get_smtp()
256
+
257
+ # Send email.
258
+ to += cc
259
+ to = unique(to)
260
+ smtp.sendmail(
261
+ self.username,
262
+ to,
263
+ email
264
+ )
265
+
266
+
267
+ __call__ = send_email
268
+
269
+
270
+ def __del__(self) -> None:
271
+ """
272
+ Delete instance.
273
+ """
274
+
275
+ # Quit.
276
+ self.smtp.quit()
reykit/rexception.py ADDED
@@ -0,0 +1,339 @@
1
+ # !/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+
4
+ """
5
+ @Time : 2024-07-17 09:46:40
6
+ @Author : Rey
7
+ @Contact : reyxbo@163.com
8
+ @Explain : Exception methods.
9
+ """
10
+
11
+
12
+ # !/usr/bin/env python
13
+ # -*- coding: utf-8 -*-
14
+
15
+ """
16
+ @Time : 2022-12-05 14:09:42
17
+ @Author : Rey
18
+ @Contact : reyxbo@163.com
19
+ @Explain : Interpreter system methods.
20
+ """
21
+
22
+
23
+ from typing import Any, Optional, Union, NoReturn
24
+ from types import TracebackType
25
+ from collections.abc import Iterable
26
+ from sys import exc_info as sys_exc_info
27
+ from os.path import exists as os_exists
28
+ from traceback import format_exc
29
+ from warnings import warn as warnings_warn
30
+
31
+ from .rtype import RNull
32
+
33
+
34
+ __all__ = (
35
+ 'RError',
36
+ 'RActiveError',
37
+ 'throw',
38
+ 'warn',
39
+ 'catch_exc',
40
+ 'check_least_one',
41
+ 'check_most_one',
42
+ 'check_file_found',
43
+ 'check_file_exist',
44
+ 'check_response_code'
45
+ )
46
+
47
+
48
+ class RError(Exception):
49
+ """
50
+ Rey `error` type.
51
+ """
52
+
53
+
54
+ class RActiveError(RError):
55
+ """
56
+ Rey's `active error` type.
57
+ """
58
+
59
+
60
+ def throw(
61
+ exception: type[BaseException] = AssertionError,
62
+ value: Any = RNull,
63
+ *values: Any,
64
+ text: Optional[str] = None,
65
+ frame: int = 2
66
+ ) -> NoReturn:
67
+ """
68
+ Throw exception.
69
+
70
+ Parameters
71
+ ----------
72
+ exception : Exception Type.
73
+ value : Exception value.
74
+ values : Exception values.
75
+ text : Exception text.
76
+ frame : Number of code to upper level.
77
+ """
78
+
79
+ # Text.
80
+ if text is None:
81
+ if exception.__doc__ is not None:
82
+ text = exception.__doc__.strip()
83
+ if (
84
+ text is None
85
+ or text == ''
86
+ ):
87
+ text = 'use error'
88
+ else:
89
+ text = text[0].lower() + text[1:]
90
+
91
+ ## Value.
92
+ if value is not RNull:
93
+ values = (value,) + values
94
+
95
+ ### Name.
96
+ from .rsystem import get_name
97
+ name = get_name(value, frame)
98
+ names = (name,)
99
+ if values != ():
100
+ names_values = get_name(values)
101
+ if names_values is not None:
102
+ names += names_values
103
+
104
+ ### Convert.
105
+ match exception:
106
+ case TypeError():
107
+ values = [
108
+ value.__class__
109
+ for value in values
110
+ if value is not None
111
+ ]
112
+ case TimeoutError():
113
+ values = [
114
+ int(value)
115
+ if value % 1 == 0
116
+ else round(value, 3)
117
+ for value in values
118
+ if value.__class__ == float
119
+ ]
120
+ values = [
121
+ repr(value)
122
+ for value in values
123
+ ]
124
+
125
+ ### Join.
126
+ if names == ():
127
+ values_len = len(values)
128
+ text_value = ', '.join(values)
129
+ if values_len == 1:
130
+ text_value = 'value is ' + text_value
131
+ else:
132
+ text_value = 'values is (%s)' % text_value
133
+ else:
134
+ names_values = zip(names, values)
135
+ text_value = ', '.join(
136
+ [
137
+ 'parameter "%s" is %s' % (name, value)
138
+ for name, value in names_values
139
+ ]
140
+ )
141
+ text += ' %s.' % text_value
142
+
143
+ # Throw exception.
144
+ exception = exception(text)
145
+ raise exception
146
+
147
+
148
+ def warn(
149
+ *infos: Any,
150
+ exception: type[BaseException] = UserWarning,
151
+ stacklevel: int = 3
152
+ ) -> None:
153
+ """
154
+ Throw warning.
155
+
156
+ Parameters
157
+ ----------
158
+ infos : Warn informations.
159
+ exception : Exception type.
160
+ stacklevel : Warning code location, number of recursions up the code level.
161
+ """
162
+
163
+ # Handle parameter.
164
+ if infos == ():
165
+ infos = 'Warning!'
166
+ elif len(infos) == 1:
167
+ if infos[0].__class__ == str:
168
+ infos = infos[0]
169
+ else:
170
+ infos = str(infos[0])
171
+ else:
172
+ infos = str(infos)
173
+
174
+ # Throw warning.
175
+ warnings_warn(infos, exception, stacklevel)
176
+
177
+
178
+ def catch_exc(
179
+ title: Optional[str] = None
180
+ ) -> tuple[str, type[BaseException], BaseException, TracebackType]:
181
+ """
182
+ Catch exception information and print, must used in `except` syntax.
183
+
184
+ Parameters
185
+ ----------
186
+ title : Print title.
187
+ - `None`: Not print.
188
+ - `str`: Print and use this title.
189
+
190
+ Returns
191
+ -------
192
+ Exception data.
193
+ - `str`: Exception report text.
194
+ - `type[BaseException]`: Exception type.
195
+ - `BaseException`: Exception instance.
196
+ - `TracebackType`: Exception traceback instance.
197
+ """
198
+
199
+ # Get parameter.
200
+ exc_report = format_exc()
201
+ exc_report = exc_report.strip()
202
+ exc_type, exc_instance, exc_traceback = sys_exc_info()
203
+
204
+ # Print.
205
+ if title is not None:
206
+
207
+ ## Import.
208
+ from .rstdout import echo
209
+
210
+ ## Execute.
211
+ echo(exc_report, title=title, frame='half')
212
+
213
+ return exc_report, exc_type, exc_instance, exc_traceback
214
+
215
+
216
+ def check_least_one(*values: Any) -> None:
217
+ """
218
+ Check that at least one of multiple values is not null, when check fail, then throw exception.
219
+
220
+ Parameters
221
+ ----------
222
+ values : Check values.
223
+ """
224
+
225
+ # Check.
226
+ for value in values:
227
+ if value is not None:
228
+ return
229
+
230
+ # Throw exception.
231
+ from .rsystem import get_name
232
+ vars_name = get_name(values)
233
+ if vars_name is not None:
234
+ vars_name_de_dup = list(set(vars_name))
235
+ vars_name_de_dup.sort(key=vars_name.index)
236
+ vars_name_str = ' ' + ' and '.join([f'"{var_name}"' for var_name in vars_name_de_dup])
237
+ else:
238
+ vars_name_str = ''
239
+ raise TypeError(f'at least one of parameters{vars_name_str} is not None')
240
+
241
+
242
+ def check_most_one(*values: Any) -> None:
243
+ """
244
+ Check that at most one of multiple values is not null, when check fail, then throw exception.
245
+
246
+ Parameters
247
+ ----------
248
+ values : Check values.
249
+ """
250
+
251
+ # Check.
252
+ none_count = 0
253
+ for value in values:
254
+ if value is not None:
255
+ none_count += 1
256
+
257
+ # Throw exception.
258
+ if none_count > 1:
259
+ from .rsystem import get_name
260
+ vars_name = get_name(values)
261
+ if vars_name is not None:
262
+ vars_name_de_dup = list(set(vars_name))
263
+ vars_name_de_dup.sort(key=vars_name.index)
264
+ vars_name_str = ' ' + ' and '.join([f'"{var_name}"' for var_name in vars_name_de_dup])
265
+ else:
266
+ vars_name_str = ''
267
+ raise TypeError(f'at most one of parameters{vars_name_str} is not None')
268
+
269
+
270
+ def check_file_found(path: str) -> None:
271
+ """
272
+ Check if file path found, if not, throw exception.
273
+
274
+ Parameters
275
+ ----------
276
+ path : File path.
277
+ """
278
+
279
+ # Check.
280
+ exist = os_exists(path)
281
+
282
+ # Throw exception.
283
+ if not exist:
284
+ throw(FileNotFoundError, path)
285
+
286
+
287
+ def check_file_exist(path: str) -> None:
288
+ """
289
+ Check if file path exist, if exist, throw exception.
290
+
291
+ Parameters
292
+ ----------
293
+ path : File path.
294
+ """
295
+
296
+ # Check.
297
+ exist = os_exists(path)
298
+
299
+ # Throw exception.
300
+ if exist:
301
+ throw(FileExistsError, path)
302
+
303
+
304
+ def check_response_code(
305
+ code: int,
306
+ range_: Optional[Union[int, Iterable[int]]] = None
307
+ ) -> bool:
308
+ """
309
+ Check if the response code is in range.
310
+
311
+ Parameters
312
+ ----------
313
+ code : Response code.
314
+ range_ : Pass the code range.
315
+ - `None`: Check if is between 200 and 299.
316
+ - `int`: Check if is this value.
317
+ - `Iterable`: Check if is in sequence.
318
+
319
+ Returns
320
+ -------
321
+ Check result.
322
+ """
323
+
324
+ # Check.
325
+ match range_:
326
+ case None:
327
+ result = code // 100 == 2
328
+ case int():
329
+ result = code == range_
330
+ case _ if hasattr(range_, '__contains__'):
331
+ result = code in range_
332
+ case _:
333
+ throw(TypeError, range_)
334
+
335
+ # Throw exception.
336
+ if not result:
337
+ throw(value=code)
338
+
339
+ return result