python-plugins 0.2.1__tar.gz → 0.2.3__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.
Files changed (94) hide show
  1. {python_plugins-0.2.1 → python_plugins-0.2.3}/CHANGES.rst +17 -3
  2. {python_plugins-0.2.1 → python_plugins-0.2.3}/PKG-INFO +3 -4
  3. {python_plugins-0.2.1 → python_plugins-0.2.3}/docs/usage.rst +1 -1
  4. {python_plugins-0.2.1 → python_plugins-0.2.3}/pyproject.toml +0 -1
  5. python_plugins-0.2.3/requirements/develop.in +1 -0
  6. {python_plugins-0.2.1 → python_plugins-0.2.3}/requirements/test.in +0 -1
  7. python_plugins-0.2.3/src/python_plugins/__about__.py +1 -0
  8. python_plugins-0.2.3/src/python_plugins/crypto/zipmix.py +264 -0
  9. python_plugins-0.2.3/src/python_plugins/email/__init__.py +1 -0
  10. python_plugins-0.2.3/src/python_plugins/email/smtp.py +51 -0
  11. python_plugins-0.2.3/src/python_plugins/utils/update-icons.py +49 -0
  12. python_plugins-0.2.3/tests/test_email.py +83 -0
  13. python_plugins-0.2.3/tests/test_zipmix.py +142 -0
  14. python_plugins-0.2.3/tests/tmp/a.png +0 -0
  15. python_plugins-0.2.3/tests/tmp/a.txt +1 -0
  16. python_plugins-0.2.3/tests/tmp/test.txt +2 -0
  17. python_plugins-0.2.3/tests/tmp/testdir/test2.txt +2 -0
  18. python_plugins-0.2.1/src/python_plugins/__about__.py +0 -1
  19. python_plugins-0.2.1/src/python_plugins/email/smtp.py +0 -34
  20. python_plugins-0.2.1/src/python_plugins/forms/__init__.py +0 -0
  21. python_plugins-0.2.1/src/python_plugins/forms/fields/__init__.py +0 -2
  22. python_plugins-0.2.1/src/python_plugins/forms/fields/datetime.py +0 -100
  23. python_plugins-0.2.1/src/python_plugins/forms/fields/json.py +0 -19
  24. python_plugins-0.2.1/src/python_plugins/forms/fields/select.py +0 -123
  25. python_plugins-0.2.1/src/python_plugins/forms/fields/switch.py +0 -10
  26. python_plugins-0.2.1/src/python_plugins/forms/fields/taglist.py +0 -9
  27. python_plugins-0.2.1/src/python_plugins/forms/mixins/__init__.py +0 -0
  28. python_plugins-0.2.1/src/python_plugins/forms/mixins/user.py +0 -29
  29. python_plugins-0.2.1/src/python_plugins/forms/widgets/__init__.py +0 -0
  30. python_plugins-0.2.1/src/python_plugins/forms/widgets/datetime.py +0 -30
  31. python_plugins-0.2.1/src/python_plugins/forms/widgets/file.py +0 -4
  32. python_plugins-0.2.1/src/python_plugins/forms/widgets/select.py +0 -26
  33. python_plugins-0.2.1/src/python_plugins/models/__init__.py +0 -0
  34. python_plugins-0.2.1/tests/test_email.py +0 -25
  35. python_plugins-0.2.1/tests/test_forms.py +0 -65
  36. {python_plugins-0.2.1 → python_plugins-0.2.3}/.github/workflows/release.yml +0 -0
  37. {python_plugins-0.2.1 → python_plugins-0.2.3}/.gitignore +0 -0
  38. {python_plugins-0.2.1 → python_plugins-0.2.3}/.readthedocs.yaml +0 -0
  39. {python_plugins-0.2.1 → python_plugins-0.2.3}/LICENSE.rst +0 -0
  40. {python_plugins-0.2.1 → python_plugins-0.2.3}/README.rst +0 -0
  41. {python_plugins-0.2.1 → python_plugins-0.2.3}/docs/Makefile +0 -0
  42. {python_plugins-0.2.1 → python_plugins-0.2.3}/docs/changes.rst +0 -0
  43. {python_plugins-0.2.1 → python_plugins-0.2.3}/docs/conf.py +0 -0
  44. {python_plugins-0.2.1 → python_plugins-0.2.3}/docs/index.rst +0 -0
  45. {python_plugins-0.2.1 → python_plugins-0.2.3}/docs/make.bat +0 -0
  46. {python_plugins-0.2.1 → python_plugins-0.2.3}/docs/requirements.txt +0 -0
  47. {python_plugins-0.2.1 → python_plugins-0.2.3}/requirements/build.in +0 -0
  48. {python_plugins-0.2.1 → python_plugins-0.2.3}/src/python_plugins/__init__.py +0 -0
  49. {python_plugins-0.2.1 → python_plugins-0.2.3}/src/python_plugins/convert/__init__.py +0 -0
  50. {python_plugins-0.2.1 → python_plugins-0.2.3}/src/python_plugins/convert/datetime_str.py +0 -0
  51. {python_plugins-0.2.1 → python_plugins-0.2.3}/src/python_plugins/convert/pretty.py +0 -0
  52. {python_plugins-0.2.1 → python_plugins-0.2.3}/src/python_plugins/convert/xml.py +0 -0
  53. {python_plugins-0.2.1 → python_plugins-0.2.3}/src/python_plugins/crypto/__init__.py +0 -0
  54. {python_plugins-0.2.1 → python_plugins-0.2.3}/src/python_plugins/crypto/fernet.py +0 -0
  55. {python_plugins-0.2.1 → python_plugins-0.2.3}/src/python_plugins/crypto/file_to_file.py +0 -0
  56. {python_plugins-0.2.1 → python_plugins-0.2.3}/src/python_plugins/crypto/str_to_list.py +0 -0
  57. {python_plugins-0.2.1 → python_plugins-0.2.3}/src/python_plugins/dumps/__init__.py +0 -0
  58. {python_plugins-0.2.1 → python_plugins-0.2.3}/src/python_plugins/dumps/postgresql_dump.py +0 -0
  59. {python_plugins-0.2.1 → python_plugins-0.2.3}/src/python_plugins/examples/higher_order_functions.py +0 -0
  60. {python_plugins-0.2.1 → python_plugins-0.2.3}/src/python_plugins/hashes/__init__.py +0 -0
  61. {python_plugins-0.2.1 → python_plugins-0.2.3}/src/python_plugins/hashes/hash.py +0 -0
  62. {python_plugins-0.2.1 → python_plugins-0.2.3}/src/python_plugins/jwt/__init__.py +0 -0
  63. {python_plugins-0.2.1 → python_plugins-0.2.3}/src/python_plugins/jwt/jwt.py +0 -0
  64. {python_plugins-0.2.1/src/python_plugins/email → python_plugins-0.2.3/src/python_plugins/models}/__init__.py +0 -0
  65. {python_plugins-0.2.1 → python_plugins-0.2.3}/src/python_plugins/models/mixins/__init__.py +0 -0
  66. {python_plugins-0.2.1 → python_plugins-0.2.3}/src/python_plugins/models/mixins/data_mixin.py +0 -0
  67. {python_plugins-0.2.1 → python_plugins-0.2.3}/src/python_plugins/models/mixins/primary_key_mixin.py +0 -0
  68. {python_plugins-0.2.1 → python_plugins-0.2.3}/src/python_plugins/models/mixins/timestamp_mixin.py +0 -0
  69. {python_plugins-0.2.1 → python_plugins-0.2.3}/src/python_plugins/models/mixins/token_minxin.py +0 -0
  70. {python_plugins-0.2.1 → python_plugins-0.2.3}/src/python_plugins/models/mixins/user_minxin.py +0 -0
  71. {python_plugins-0.2.1 → python_plugins-0.2.3}/src/python_plugins/models/update.py +0 -0
  72. {python_plugins-0.2.1 → python_plugins-0.2.3}/src/python_plugins/process/__init__.py +0 -0
  73. {python_plugins-0.2.1 → python_plugins-0.2.3}/src/python_plugins/process/python_venv_process.py +0 -0
  74. {python_plugins-0.2.1 → python_plugins-0.2.3}/src/python_plugins/process/sub_process.py +0 -0
  75. {python_plugins-0.2.1 → python_plugins-0.2.3}/src/python_plugins/random/__init__.py +0 -0
  76. {python_plugins-0.2.1 → python_plugins-0.2.3}/src/python_plugins/random/random_str.py +0 -0
  77. {python_plugins-0.2.1 → python_plugins-0.2.3}/src/python_plugins/utils/__init__.py +0 -0
  78. {python_plugins-0.2.1 → python_plugins-0.2.3}/src/python_plugins/utils/walk_remove_dir.py +0 -0
  79. {python_plugins-0.2.1 → python_plugins-0.2.3}/src/python_plugins/weixin/biz_data_crypt.py +0 -0
  80. {python_plugins-0.2.1 → python_plugins-0.2.3}/src/python_plugins/weixin/error_code.py +0 -0
  81. {python_plugins-0.2.1 → python_plugins-0.2.3}/src/python_plugins/weixin/format_response.py +0 -0
  82. {python_plugins-0.2.1 → python_plugins-0.2.3}/src/python_plugins/weixin/wechat.py +0 -0
  83. {python_plugins-0.2.1 → python_plugins-0.2.3}/src/python_plugins/weixin/wechat_crypt.py +0 -0
  84. {python_plugins-0.2.1 → python_plugins-0.2.3}/src/python_plugins/weixin/weixin_api.py +0 -0
  85. {python_plugins-0.2.1 → python_plugins-0.2.3}/tests/__init__.py +0 -0
  86. {python_plugins-0.2.1 → python_plugins-0.2.3}/tests/conftest.py +0 -0
  87. {python_plugins-0.2.1 → python_plugins-0.2.3}/tests/test_crypt_file.py +0 -0
  88. {python_plugins-0.2.1 → python_plugins-0.2.3}/tests/test_crypto.py +0 -0
  89. {python_plugins-0.2.1 → python_plugins-0.2.3}/tests/test_jwt.py +0 -0
  90. {python_plugins-0.2.1 → python_plugins-0.2.3}/tests/test_process.py +0 -0
  91. {python_plugins-0.2.1 → python_plugins-0.2.3}/tests/test_random.py +0 -0
  92. {python_plugins-0.2.1 → python_plugins-0.2.3}/tests/test_sqlalchemy.py +0 -0
  93. {python_plugins-0.2.1 → python_plugins-0.2.3}/tests/test_utils.py +0 -0
  94. {python_plugins-0.2.1 → python_plugins-0.2.3}/tests/test_weixin.py +0 -0
@@ -1,3 +1,17 @@
1
+ v0.2.3
2
+ ------
3
+
4
+ Released 2025-11-02
5
+
6
+ - zip7
7
+
8
+ v0.2.2
9
+ ------
10
+
11
+ Released 2024-12-27
12
+
13
+ - update-icons
14
+
1
15
  v0.2.1
2
16
  ------
3
17
 
@@ -10,7 +24,7 @@ v0.2.0
10
24
 
11
25
  Released 2024-11-20
12
26
 
13
- - add forms
27
+ - add random
14
28
 
15
29
 
16
30
  v0.1.9
@@ -18,7 +32,7 @@ v0.1.9
18
32
 
19
33
  Released 2024-11-19
20
34
 
21
- - add forms
35
+ - add convert
22
36
 
23
37
 
24
38
  v0.1.8
@@ -62,7 +76,7 @@ v0.1.3
62
76
 
63
77
  Released 2024-09-19
64
78
 
65
- - support random_str,jwt,hash_file,fernet
79
+ - support hash_file,fernet
66
80
 
67
81
  v0.1.2
68
82
  ------
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.3
1
+ Metadata-Version: 2.4
2
2
  Name: python-plugins
3
- Version: 0.2.1
3
+ Version: 0.2.3
4
4
  Summary: A collection of Python functions and classes.
5
5
  Project-URL: Documentation, https://python-plugins.readthedocs.io
6
6
  Project-URL: Source, https://github.com/ojso/python-plugins
@@ -27,6 +27,7 @@ License: MIT License
27
27
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28
28
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29
29
  SOFTWARE.
30
+ License-File: LICENSE.rst
30
31
  Keywords: plugin,utils
31
32
  Classifier: Development Status :: 3 - Alpha
32
33
  Classifier: Intended Audience :: Developers
@@ -49,8 +50,6 @@ Provides-Extra: requests
49
50
  Requires-Dist: requests; extra == 'requests'
50
51
  Provides-Extra: sqlalchemy
51
52
  Requires-Dist: sqlalchemy; extra == 'sqlalchemy'
52
- Provides-Extra: wtforms
53
- Requires-Dist: wtforms; extra == 'wtforms'
54
53
  Description-Content-Type: text/x-rst
55
54
 
56
55
  python-plugins
@@ -54,7 +54,7 @@ mixins
54
54
 
55
55
  db = SQLAlchemy(model_class=Base)
56
56
 
57
- class User(db.models,PrimaryKeyMixin, DataMixin, TimestampMixin, UserMixin):
57
+ class User(db.Model,PrimaryKeyMixin, DataMixin, TimestampMixin, UserMixin):
58
58
  __tablename__ = "users"
59
59
 
60
60
  walk_remove_dir
@@ -30,7 +30,6 @@ dependencies = [
30
30
  cryptography = ["cryptography"]
31
31
  requests = ["requests"]
32
32
  sqlalchemy = ["SQLAlchemy"]
33
- wtforms = ["WTForms"]
34
33
  pillow = ["pillow"]
35
34
  qrcode = ["qrcode"]
36
35
  jwt = ["PyJWT"]
@@ -0,0 +1 @@
1
+ lxml
@@ -4,4 +4,3 @@ cryptography
4
4
  SQLAlchemy
5
5
  PyJWT
6
6
  requests
7
- WTForms
@@ -0,0 +1 @@
1
+ __version__ = "0.2.3"
@@ -0,0 +1,264 @@
1
+ import os
2
+ import random
3
+ import hashlib
4
+ import tempfile
5
+ import filecmp
6
+ from pathlib import Path
7
+ import subprocess
8
+
9
+
10
+ class ZipMix:
11
+ def __init__(self, zipcmd="7z"):
12
+ self.zipcmd = zipcmd
13
+ self.z7_available = self.check_7z_available()
14
+
15
+ def check_7z_available(self) -> bool:
16
+ try:
17
+ subprocess.run([self.zipcmd], capture_output=True, check=True)
18
+ return True
19
+ except (subprocess.CalledProcessError, FileNotFoundError):
20
+ return False
21
+
22
+ def create_mix_flag(self, pos: int, nlen: int) -> bytes:
23
+ """m{}i{}x{}{nlen}{}"""
24
+ start_bytes = pos.to_bytes(4, byteorder="big")
25
+ return b"m%si%sx%s%s%s" % (
26
+ start_bytes[0:1],
27
+ start_bytes[1:2],
28
+ start_bytes[2:3],
29
+ nlen.to_bytes(1, byteorder="big"),
30
+ start_bytes[3:4],
31
+ )
32
+
33
+ def int_to_bytes(self, pos: int):
34
+ return pos.to_bytes(4, byteorder="big")
35
+
36
+ def int_from_bytes(self, intbs):
37
+ return int.from_bytes(intbs, byteorder="big")
38
+
39
+ def extract_from_mix_flag(self, mix_flag):
40
+ fix_flag = mix_flag[0:1] + mix_flag[2:3] + mix_flag[4:5]
41
+ if fix_flag != b"mix":
42
+ raise ValueError("not match mix.")
43
+ nlen = self.int_from_bytes(mix_flag[6:7])
44
+ pos = self.int_from_bytes(
45
+ mix_flag[1:2] + mix_flag[3:4] + mix_flag[5:6] + mix_flag[7:8]
46
+ )
47
+ return (pos, nlen)
48
+
49
+ def insert_string_to_binary(self, data, insert_bytes):
50
+ n_data = len(data)
51
+ n_ibytes = len(insert_bytes)
52
+ pos_list = sorted(random.sample(range(min(n_data, 0xFFFFFFFF)), n_ibytes))
53
+
54
+ parts = []
55
+ pre_pos = 0
56
+
57
+ for i, curr_pos in enumerate(pos_list):
58
+ if i == 0:
59
+ end_flag = self.create_mix_flag(curr_pos, n_ibytes)
60
+ else:
61
+ parts.append(self.int_to_bytes(curr_pos))
62
+
63
+ parts.append(data[pre_pos:curr_pos])
64
+ parts.append(insert_bytes[i : i + 1])
65
+ pre_pos = curr_pos
66
+
67
+ parts.append(data[pre_pos:])
68
+ parts.append(end_flag)
69
+ return b"".join(parts)
70
+
71
+ def extract_string_from_binary(self, data):
72
+ start_pos, n_insert_bytes = self.extract_from_mix_flag(data[-8:])
73
+ parts = []
74
+ insert_parts = []
75
+ pos_list = [start_pos]
76
+ data_pre_pos = 0
77
+ curr_pos = start_pos
78
+
79
+ for k in range(n_insert_bytes):
80
+ data_curr_pos = curr_pos + 5 * k
81
+ parts.append(data[data_pre_pos:data_curr_pos])
82
+ insert_parts.append(data[data_curr_pos : data_curr_pos + 1])
83
+ if k < n_insert_bytes - 1:
84
+ curr_pos = self.int_from_bytes(
85
+ data[data_curr_pos + 1 : data_curr_pos + 5]
86
+ )
87
+ pos_list.append(curr_pos)
88
+ data_pre_pos = data_curr_pos + 5
89
+ else:
90
+ curr_pos = None
91
+ data_pre_pos = data_curr_pos + 1
92
+
93
+ parts.append(data[data_pre_pos:-8])
94
+ return b"".join(insert_parts), b"".join(parts)
95
+
96
+ def _generate_password(self) -> str:
97
+ return hashlib.sha256(os.urandom(1000)).hexdigest()[:32]
98
+
99
+ def zip7(self, file_or_dir, archive_path=None, pwd=None, force_pwd=False,silent = True):
100
+ path_obj = Path(file_or_dir)
101
+ if not path_obj.exists():
102
+ return None
103
+
104
+ if archive_path is None:
105
+ archive_path = file_or_dir + ".7z"
106
+
107
+ if pwd is None:
108
+ if force_pwd:
109
+ pwd = self._generate_password()
110
+ else:
111
+ pwd = pwd[:32]
112
+
113
+ cmd = [self.zipcmd, "a"]
114
+ if silent:
115
+ cmd.append("-bso0")
116
+ if pwd:
117
+ cmd.extend([f"-p{pwd}", "-mhe=on"])
118
+ cmd.extend([archive_path, file_or_dir])
119
+
120
+ try:
121
+ subprocess.run(cmd, check=True)
122
+ if pwd is not None:
123
+ return ("ok", archive_path, pwd)
124
+ else:
125
+ return ("ok", archive_path)
126
+ except subprocess.CalledProcessError as e:
127
+ return ("fail", f"{e}")
128
+
129
+ def unzip7_list_archive(self, archive_path, pwd=None,silent = True):
130
+ cmd = [self.zipcmd, "l"]
131
+ if silent:
132
+ cmd.append("-bso0")
133
+ if pwd:
134
+ cmd.append(f"-p{pwd}")
135
+ cmd.append(archive_path)
136
+
137
+ try:
138
+ subprocess.run(cmd, check=True)
139
+ return ("ok", archive_path)
140
+ except subprocess.CalledProcessError as e:
141
+ return ("fail", f"{e}")
142
+
143
+ def unzip7(self, archive_path, output_path=None, pwd=None, overwrite: bool = True,silent = True):
144
+ archive_obj = Path(archive_path)
145
+
146
+ if output_path is None:
147
+ output_dir = archive_obj.parent
148
+ else:
149
+ output_dir = Path(output_path)
150
+
151
+ if not output_dir.exists():
152
+ output_dir.mkdir(parents=True, exist_ok=True)
153
+
154
+ if not output_dir.is_dir():
155
+ return ("fail", f"{output_dir} is not dir")
156
+
157
+ cmd = [self.zipcmd, "x", "-y"]
158
+ if silent:
159
+ cmd.append("-bso0")
160
+ if not overwrite:
161
+ cmd.append("-aos")
162
+ if pwd:
163
+ cmd.append(f"-p{pwd}")
164
+ cmd.extend([archive_path, f"-o{output_dir}"])
165
+
166
+ try:
167
+ subprocess.run(cmd, check=True)
168
+ return ("ok", f"{output_dir}")
169
+ except subprocess.CalledProcessError as e:
170
+ return ("fail", f"{e}")
171
+
172
+ def zip7mix(self, file_or_dir, archive_path=None,silent = True):
173
+ path_obj = Path(file_or_dir)
174
+ if not path_obj.exists():
175
+ return None
176
+
177
+ if archive_path is None:
178
+ archive_path = file_or_dir + ".7z"
179
+
180
+ tmp_archive_path = archive_path+".tmp"
181
+
182
+ tmp_obj = Path(tmp_archive_path)
183
+ if tmp_obj.exists():
184
+ tmp_obj.unlink()
185
+
186
+ pwd = self._generate_password()
187
+
188
+ cmd = [self.zipcmd, "a"]
189
+ if silent:
190
+ cmd.append("-bso0")
191
+ cmd.extend([f"-p{pwd}", "-mhe=on"])
192
+ cmd.extend([tmp_archive_path, file_or_dir])
193
+
194
+ try:
195
+ subprocess.run(cmd, check=True)
196
+ except subprocess.CalledProcessError as e:
197
+ return ("fail", f"{e}")
198
+
199
+ with open(tmp_archive_path,"rb") as f1:
200
+ data = f1.read()
201
+
202
+ # remove tmp file
203
+ tmp_obj.unlink()
204
+
205
+ new_data = self.insert_string_to_binary(data,pwd.encode('utf-8'))
206
+
207
+ # append fake 7z tail
208
+ new_data += data[-100:]
209
+
210
+ with open(archive_path,"wb") as f2:
211
+ f2.write(new_data)
212
+
213
+ return ("ok", archive_path)
214
+
215
+ def unzip7mix(self, archive_path, output_path=None, overwrite: bool = True,silent = True):
216
+ archive_obj = Path(archive_path)
217
+
218
+ tmp_archive_path = archive_path+".tmp"
219
+
220
+ tmp_obj = Path(tmp_archive_path)
221
+ if tmp_obj.exists():
222
+ tmp_obj.unlink()
223
+
224
+ if output_path is None:
225
+ output_dir = archive_obj.parent
226
+ else:
227
+ output_dir = Path(output_path)
228
+
229
+ if not output_dir.exists():
230
+ output_dir.mkdir(parents=True, exist_ok=True)
231
+
232
+ if not output_dir.is_dir():
233
+ return ("fail", f"{output_dir} is not dir")
234
+
235
+ with open(archive_path,"rb") as f1:
236
+ data = f1.read()
237
+ # remove fake 7z tail
238
+ data = data[:-100]
239
+
240
+ pwdbts,new_data = self.extract_string_from_binary(data)
241
+
242
+ pwd = pwdbts.decode("utf-8")
243
+
244
+ with open(tmp_archive_path,"wb") as f2:
245
+ f2.write(new_data)
246
+
247
+ cmd = [self.zipcmd, "x", "-y"]
248
+ if silent:
249
+ cmd.append("-bso0")
250
+ if not overwrite:
251
+ cmd.append("-aos")
252
+ cmd.append(f"-p{pwd}")
253
+ cmd.extend([tmp_archive_path, f"-o{output_dir}"])
254
+
255
+ try:
256
+ subprocess.run(cmd, check=True)
257
+ tmp_obj.unlink()
258
+ return ("ok", f"{output_dir}")
259
+ except subprocess.CalledProcessError as e:
260
+ return ("fail", f"{e}")
261
+
262
+ def filecmp(self,f1,f2):
263
+ result = filecmp.cmp(f1, f2, shallow=False)
264
+ return result
@@ -0,0 +1 @@
1
+ from .smtp import SmtpSSL
@@ -0,0 +1,51 @@
1
+ import smtplib
2
+ from email.message import EmailMessage
3
+ import mimetypes
4
+
5
+
6
+ class SmtpSSL:
7
+ def __init__(self, host, port, user, password):
8
+ self.host = host
9
+ self.port = port
10
+ self.user = user
11
+ self.password = password
12
+
13
+ def send_emsg(self, data):
14
+ emsg = EmailMessage()
15
+ emsg["Subject"] = data["subject"]
16
+ emsg["From"] = data.get("From", self.user)
17
+ emsg["To"] = data["to"]
18
+
19
+ if "cc" in data:
20
+ emsg["Cc"] = data["cc"]
21
+
22
+ if "bcc" in data:
23
+ emsg["Bcc"] = data["bcc"]
24
+
25
+ # Check if HTML content is provided
26
+ if "html_content" in data:
27
+ emsg.add_alternative(data["html_content"], subtype="html")
28
+ else:
29
+ emsg.set_content(data["content"])
30
+
31
+ # Add attachments if provided
32
+ if "attachments" in data:
33
+ for file_path in data["attachments"]:
34
+ ctype, encoding = mimetypes.guess_type(file_path)
35
+ if ctype is None or encoding is not None:
36
+ ctype = "application/octet-stream"
37
+ maintype, subtype = ctype.split("/", 1)
38
+
39
+ with open(file_path, "rb") as f:
40
+ emsg.add_attachment(
41
+ f.read(),
42
+ maintype=maintype,
43
+ subtype=subtype,
44
+ filename=file_path.split("\\")[-1],
45
+ )
46
+
47
+ with smtplib.SMTP_SSL(self.host, self.port) as smtp:
48
+ # smtp.set_debuglevel(1)
49
+ smtp.login(self.user, self.password)
50
+ senderrs = smtp.send_message(emsg)
51
+ return senderrs
@@ -0,0 +1,49 @@
1
+ """Generate example page with overview of all icons."""
2
+
3
+ from lxml import etree
4
+
5
+
6
+ def parse(filename):
7
+ """Parse SVG file containing icons."""
8
+ symbols = set()
9
+ data = etree.parse(filename)
10
+ svg = data.getroot()
11
+ for symbol in svg:
12
+ symbols.add(symbol.attrib["id"])
13
+ return sorted(symbols)
14
+
15
+
16
+ def generate(version):
17
+ """Write the HTML template file."""
18
+ head = f"""{{% extends "base.html" %}}
19
+ {{% from 'macro/icon.html' import render_icon %}}
20
+ <!-- DO NOT EDIT! Use update-icons.py for updating this file. -->
21
+ {{% block content %}}
22
+ <h2>Icons</h2>
23
+ <p>These are all the icons which are currently supported by Bootstrap-Flask.</p>
24
+ <ul class="row row-cols-3 row-cols-sm-4 row-cols-lg-6 row-cols-xl-8 list-unstyled list">
25
+ """
26
+ names = parse(f"icons/bootstrap-icons.svg")
27
+ with open(f"bootstrap{version}/templates/icons.html", "w") as file:
28
+ file.write(head)
29
+ number = 0
30
+ for name in sorted(names):
31
+ item = f"""<li class="col mb-4">
32
+ <a class="d-block text-dark text-body-emphasis text-decoration-none" href="https://icons.getbootstrap.com/icons/{name}/">
33
+ <div class="px-3 py-4 mb-2 bg-light text-center rounded">
34
+ {{{{ render_icon('{name}', 32) }}}}
35
+ </div>
36
+ </a>
37
+ <div class="name text-body-secondary text-decoration-none text-center pt-1">{name}</div>
38
+ </li>
39
+ """
40
+ file.write(item)
41
+ number += 1
42
+ file.write("</ul>\n")
43
+ file.write(f"<p>This is a total of {number} icons.</p>\n")
44
+ file.write("{% endblock %}\n")
45
+ print(f"For Bootstrap{version}, a total of {number} icons are supported.")
46
+
47
+
48
+ for value in (4, 5):
49
+ generate(value)
@@ -0,0 +1,83 @@
1
+ import os.path as op
2
+ import pytest
3
+ from python_plugins.email import SmtpSSL
4
+
5
+ @pytest.mark.skip(reason="test ok")
6
+ def test_send_content():
7
+ host = "smtp.qiye.aliyun.com"
8
+ port = "465"
9
+ user = "test@ojso.com"
10
+ password = "gTk94GEeNvujr4zm"
11
+
12
+ to = "test@ojso.com"
13
+ data = {
14
+ "to": to,
15
+ "subject": "test send content",
16
+ "content": "This is a plain text email.",
17
+ }
18
+
19
+ s = SmtpSSL(host, port, user, password)
20
+ r = s.send_emsg(data)
21
+
22
+ assert not r
23
+
24
+ @pytest.mark.skip(reason="test ok")
25
+ def test_send_html():
26
+ host = "smtp.qiye.aliyun.com"
27
+ port = "465"
28
+ user = "test@ojso.com"
29
+ password = "gTk94GEeNvujr4zm"
30
+
31
+ to = "test@ojso.com"
32
+ html_content = """
33
+ <html>
34
+ <body>
35
+ <h1>This is a HTML email.</h1>
36
+ <p>You can use <strong>HTML</strong> format!</p>
37
+ <p><a href="https://www.example.com">Click here to access the sample website</a></p>
38
+ </body>
39
+ </html>
40
+ """
41
+
42
+ data = {
43
+ "to": to,
44
+ "subject": "test send html",
45
+ "html_content": html_content,
46
+ }
47
+
48
+ s = SmtpSSL(host, port, user, password)
49
+ r = s.send_emsg(data)
50
+
51
+ assert not r
52
+
53
+ @pytest.mark.skip(reason="test ok")
54
+ def test_send_attach():
55
+ host = "smtp.qiye.aliyun.com"
56
+ port = "465"
57
+ user = "test@ojso.com"
58
+ password = "gTk94GEeNvujr4zm"
59
+
60
+ to = "test@ojso.com"
61
+ html_content = """
62
+ <html>
63
+ <body>
64
+ <h1>This a HTML email with attach</h1>
65
+ <p>have a look at attach</p>
66
+ </body>
67
+ </html>
68
+ """
69
+ parent_dir = op.realpath(op.dirname(__file__))
70
+ attach_path_1 = op.join(parent_dir, "tmp","a.txt")
71
+ attach_path_2 = op.join(parent_dir, "tmp","a.png")
72
+
73
+ data = {
74
+ "to": to,
75
+ "subject": "test send attach",
76
+ "html_content": html_content,
77
+ "attachments": [attach_path_1, attach_path_2],
78
+ }
79
+
80
+ s = SmtpSSL(host, port, user, password)
81
+ r = s.send_emsg(data)
82
+
83
+ assert not r
@@ -0,0 +1,142 @@
1
+ import pytest
2
+ import os
3
+ import os.path as op
4
+ import shutil
5
+ from python_plugins.crypto.zipmix import ZipMix
6
+
7
+ def test_z7_available():
8
+ zm = ZipMix()
9
+ assert zm.z7_available is True
10
+
11
+ def test_int_bytes_roundtrip():
12
+ zm = ZipMix()
13
+ for v in (0, 1, 255, 256, 65535, 2**24 - 1):
14
+ b = zm.int_to_bytes(v)
15
+ assert isinstance(b, bytes)
16
+ assert zm.int_from_bytes(b) == v
17
+
18
+
19
+ def test_create_and_extract_mix_flag():
20
+ zm = ZipMix()
21
+ pos = 0x01020304
22
+ nlen = 7
23
+ flag = zm.create_mix_flag(pos, nlen)
24
+ assert isinstance(flag, bytes)
25
+ assert len(flag) == 8
26
+ extracted = zm.extract_from_mix_flag(flag)
27
+ assert extracted == (pos, nlen)
28
+ with pytest.raises(ValueError):
29
+ zm.extract_from_mix_flag(b"0" * 8)
30
+
31
+
32
+ def test_insert_string_to_binary():
33
+ zm = ZipMix()
34
+ data = os.urandom(500)
35
+ insert = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
36
+ new_1 = zm.insert_string_to_binary(data, insert)
37
+ assert len(new_1) - len(data) == len(insert) * 5 + 4
38
+ old_insert, old = zm.extract_string_from_binary(new_1)
39
+ assert old_insert == insert
40
+ assert data == old
41
+ new_2 = zm.insert_string_to_binary(data, insert)
42
+ assert len(new_2) == len(new_1)
43
+ assert new_2 != new_1
44
+ old_insert, old = zm.extract_string_from_binary(new_2)
45
+ assert old_insert == insert
46
+ assert data == old
47
+
48
+
49
+ def test_zip7():
50
+ parent_dir = op.realpath(op.dirname(__file__))
51
+ f_test = op.join(parent_dir, "tmp","test.txt")
52
+ f_test_7z = op.join(parent_dir, "tmp","test.txt.7z")
53
+ if os.path.exists(f_test_7z):
54
+ os.unlink(f_test_7z)
55
+ dir_test_out = op.join(parent_dir, "tmp","out")
56
+ f_test_out = op.join(parent_dir, "tmp","out","test.txt")
57
+
58
+ zm = ZipMix()
59
+ r_zip=zm.zip7(f_test)
60
+ assert len(r_zip)==2
61
+ assert r_zip[0] == "ok"
62
+ f_zip = r_zip[1]
63
+ # r_list = zm.unzip7_list_archive(f_zip)
64
+ # assert r_list[0] == "ok"
65
+ r_unzip = zm.unzip7(f_zip,dir_test_out)
66
+ assert r_unzip[0] == "ok"
67
+ r_cmp = zm.filecmp(f_test,f_test_out)
68
+ assert r_cmp is True
69
+ if os.path.exists(f_zip):
70
+ os.unlink(f_zip)
71
+ if os.path.exists(f_test_out):
72
+ os.unlink(f_test_out)
73
+
74
+ def test_zip7_dir():
75
+ parent_dir = op.realpath(op.dirname(__file__))
76
+ f_test = op.join(parent_dir, "tmp","testdir")
77
+ f_test_7z = op.join(parent_dir, "tmp","testdir.7z")
78
+ if os.path.exists(f_test_7z):
79
+ os.unlink(f_test_7z)
80
+ dir_test_out = op.join(parent_dir, "tmp","out","testdir")
81
+ zm = ZipMix()
82
+ r_zip=zm.zip7(f_test)
83
+ assert len(r_zip)==2
84
+ assert r_zip[0] == "ok"
85
+ f_zip = r_zip[1]
86
+ # r_list = zm.unzip7_list_archive(f_zip)
87
+ # assert r_list[0] == "ok"
88
+ r_unzip = zm.unzip7(f_zip,dir_test_out)
89
+ assert r_unzip[0] == "ok"
90
+ # r_cmp = zm.filecmp(f_test,f_test_out)
91
+ # assert r_cmp is True
92
+ if os.path.exists(f_zip):
93
+ os.unlink(f_zip)
94
+ if os.path.exists(dir_test_out):
95
+ shutil.rmtree(dir_test_out)
96
+
97
+ def test_zip7_force_password():
98
+ parent_dir = op.realpath(op.dirname(__file__))
99
+ f_test = op.join(parent_dir, "tmp","test.txt")
100
+ f_test_7z = op.join(parent_dir, "tmp","test.txt.7z")
101
+ if os.path.exists(f_test_7z):
102
+ os.unlink(f_test_7z)
103
+ dir_test_out = op.join(parent_dir, "tmp","out")
104
+ f_test_out = op.join(parent_dir, "tmp","out","test.txt")
105
+ zm = ZipMix()
106
+ r_zip=zm.zip7(f_test,force_pwd=True)
107
+ assert len(r_zip)==3
108
+ assert r_zip[0] == "ok"
109
+ f_zip = r_zip[1]
110
+ pwd = r_zip[2]
111
+ # r_list = zm.unzip7_list_archive(f_zip)
112
+ # assert r_list[0] == "ok"
113
+ r_unzip = zm.unzip7(f_zip,dir_test_out,pwd=pwd)
114
+ assert r_unzip[0] == "ok"
115
+ r_cmp = zm.filecmp(f_test,f_test_out)
116
+ assert r_cmp is True
117
+ if os.path.exists(f_zip):
118
+ os.unlink(f_zip)
119
+ if os.path.exists(f_test_out):
120
+ os.unlink(f_test_out)
121
+
122
+ def test_zip7_mix():
123
+ parent_dir = op.realpath(op.dirname(__file__))
124
+ f_test = op.join(parent_dir, "tmp","test.txt")
125
+ f_test_7z = op.join(parent_dir, "tmp","test.txt.7z")
126
+ if os.path.exists(f_test_7z):
127
+ os.unlink(f_test_7z)
128
+ dir_test_out = op.join(parent_dir, "tmp","out")
129
+ f_test_out = op.join(parent_dir, "tmp","out","test.txt")
130
+ zm = ZipMix()
131
+ r_zip=zm.zip7mix(f_test)
132
+ assert len(r_zip)==2
133
+ assert r_zip[0] == "ok"
134
+ f_zipmix = r_zip[1]
135
+ r_unzip = zm.unzip7mix(f_zipmix,dir_test_out)
136
+ assert r_unzip[0] == "ok"
137
+ r_cmp = zm.filecmp(f_test,f_test_out)
138
+ assert r_cmp is True
139
+ if os.path.exists(f_zipmix):
140
+ os.unlink(f_zipmix)
141
+ if os.path.exists(f_test_out):
142
+ os.unlink(f_test_out)
Binary file