python-plugins 0.1.2__tar.gz → 0.1.4__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.
- {python_plugins-0.1.2 → python_plugins-0.1.4}/CHANGES.rst +15 -0
- {python_plugins-0.1.2 → python_plugins-0.1.4}/PKG-INFO +3 -1
- {python_plugins-0.1.2 → python_plugins-0.1.4}/docs/usage.rst +9 -0
- {python_plugins-0.1.2 → python_plugins-0.1.4}/pyproject.toml +1 -0
- python_plugins-0.1.4/requirements/test.in +5 -0
- python_plugins-0.1.4/src/python_plugins/__about__.py +1 -0
- python_plugins-0.1.4/src/python_plugins/convert/__init__.py +2 -0
- python_plugins-0.1.4/src/python_plugins/convert/datetime_str.py +15 -0
- python_plugins-0.1.4/src/python_plugins/convert/xml.py +7 -0
- python_plugins-0.1.4/src/python_plugins/crypto/__init__.py +1 -0
- python_plugins-0.1.4/src/python_plugins/crypto/fernet.py +41 -0
- python_plugins-0.1.4/src/python_plugins/crypto/str_to_list.py +91 -0
- python_plugins-0.1.4/src/python_plugins/dumps/__init__.py +2 -0
- python_plugins-0.1.4/src/python_plugins/dumps/postgresql_dump.py +31 -0
- python_plugins-0.1.4/src/python_plugins/hashes/__init__.py +1 -0
- python_plugins-0.1.4/src/python_plugins/hashes/hash.py +26 -0
- python_plugins-0.1.4/src/python_plugins/jwt/__init__.py +1 -0
- python_plugins-0.1.4/src/python_plugins/jwt/jwt.py +37 -0
- python_plugins-0.1.4/src/python_plugins/models/update.py +33 -0
- python_plugins-0.1.4/src/python_plugins/process/__init__.py +2 -0
- python_plugins-0.1.4/src/python_plugins/process/python_venv_process.py +18 -0
- python_plugins-0.1.4/src/python_plugins/process/sub_process.py +12 -0
- python_plugins-0.1.4/src/python_plugins/random/__init__.py +4 -0
- python_plugins-0.1.4/src/python_plugins/random/random_str.py +46 -0
- python_plugins-0.1.4/src/python_plugins/utils/remove_pycache.py +13 -0
- python_plugins-0.1.4/tests/test_crypto.py +82 -0
- python_plugins-0.1.4/tests/test_jwt.py +25 -0
- python_plugins-0.1.4/tests/test_process.py +9 -0
- python_plugins-0.1.4/tests/test_random.py +8 -0
- python_plugins-0.1.4/tests/test_sqlalchemy.py +29 -0
- python_plugins-0.1.4/tests/test_utils.py +8 -0
- python_plugins-0.1.2/requirements/test.in +0 -2
- python_plugins-0.1.2/src/python_plugins/__about__.py +0 -1
- {python_plugins-0.1.2 → python_plugins-0.1.4}/.github/workflows/release.yml +0 -0
- {python_plugins-0.1.2 → python_plugins-0.1.4}/.gitignore +0 -0
- {python_plugins-0.1.2 → python_plugins-0.1.4}/.readthedocs.yaml +0 -0
- {python_plugins-0.1.2 → python_plugins-0.1.4}/LICENSE.rst +0 -0
- {python_plugins-0.1.2 → python_plugins-0.1.4}/README.rst +0 -0
- {python_plugins-0.1.2 → python_plugins-0.1.4}/docs/Makefile +0 -0
- {python_plugins-0.1.2 → python_plugins-0.1.4}/docs/api.rst +0 -0
- {python_plugins-0.1.2 → python_plugins-0.1.4}/docs/changes.rst +0 -0
- {python_plugins-0.1.2 → python_plugins-0.1.4}/docs/conf.py +0 -0
- {python_plugins-0.1.2 → python_plugins-0.1.4}/docs/examples.rst +0 -0
- {python_plugins-0.1.2 → python_plugins-0.1.4}/docs/index.rst +0 -0
- {python_plugins-0.1.2 → python_plugins-0.1.4}/docs/make.bat +0 -0
- {python_plugins-0.1.2 → python_plugins-0.1.4}/docs/requirements.txt +0 -0
- {python_plugins-0.1.2 → python_plugins-0.1.4}/examples/README.rst +0 -0
- {python_plugins-0.1.2 → python_plugins-0.1.4}/requirements/build.in +0 -0
- {python_plugins-0.1.2 → python_plugins-0.1.4}/src/python_plugins/__init__.py +0 -0
- {python_plugins-0.1.2 → python_plugins-0.1.4}/src/python_plugins/email/__init__.py +0 -0
- {python_plugins-0.1.2 → python_plugins-0.1.4}/src/python_plugins/email/smtp.py +0 -0
- {python_plugins-0.1.2 → python_plugins-0.1.4}/src/python_plugins/models/__init__.py +0 -0
- {python_plugins-0.1.2 → python_plugins-0.1.4}/src/python_plugins/models/mixins/__init__.py +0 -0
- {python_plugins-0.1.2 → python_plugins-0.1.4}/src/python_plugins/models/mixins/data_mixin.py +0 -0
- {python_plugins-0.1.2 → python_plugins-0.1.4}/src/python_plugins/models/mixins/primary_key_mixin.py +0 -0
- {python_plugins-0.1.2 → python_plugins-0.1.4}/src/python_plugins/models/mixins/timestamp_mixin.py +0 -0
- {python_plugins-0.1.2 → python_plugins-0.1.4}/src/python_plugins/models/mixins/user_minxin.py +0 -0
- {python_plugins-0.1.2 → python_plugins-0.1.4}/tests/__init__.py +0 -0
- {python_plugins-0.1.2 → python_plugins-0.1.4}/tests/conftest.py +0 -0
- {python_plugins-0.1.2 → python_plugins-0.1.4}/tests/test_email.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: python-plugins
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.4
|
|
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
|
|
@@ -40,6 +40,8 @@ Classifier: Topic :: Software Development :: Build Tools
|
|
|
40
40
|
Requires-Python: >=3.10
|
|
41
41
|
Provides-Extra: cryptography
|
|
42
42
|
Requires-Dist: cryptography; extra == 'cryptography'
|
|
43
|
+
Provides-Extra: jwt
|
|
44
|
+
Requires-Dist: pyjwt; extra == 'jwt'
|
|
43
45
|
Provides-Extra: pillow
|
|
44
46
|
Requires-Dist: pillow; extra == 'pillow'
|
|
45
47
|
Provides-Extra: qrcode
|
|
@@ -50,3 +50,12 @@ mixins
|
|
|
50
50
|
|
|
51
51
|
class User(PrimaryKeyMixin,UserMixin, TimestampMixin,db.models):
|
|
52
52
|
pass
|
|
53
|
+
|
|
54
|
+
remove_pycache
|
|
55
|
+
=======================
|
|
56
|
+
|
|
57
|
+
.. code-block :: python
|
|
58
|
+
|
|
59
|
+
from python_plugins.utils.remove_pycache import remove_pycache
|
|
60
|
+
|
|
61
|
+
remove_pycache(".")
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.4"
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
|
|
3
|
+
date_fmt = "%Y-%m-%d"
|
|
4
|
+
datetime_fmt = "%Y-%m-%d %H:%M:%S"
|
|
5
|
+
|
|
6
|
+
# datetime.isoformat()
|
|
7
|
+
# datetime.fromisoformat(str)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def datetime2str(dt):
|
|
11
|
+
return datetime.strftime(dt, datetime_fmt)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def str2datetime(s):
|
|
15
|
+
return datetime.strptime(s, datetime_fmt)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .fernet import generate_fernet_key,fernet_encrypt,fernet_decrypt
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
from cryptography.hazmat.primitives import padding
|
|
3
|
+
from cryptography.fernet import Fernet
|
|
4
|
+
|
|
5
|
+
# Fernet文档 : https://github.com/fernet/spec/blob/master/Spec.md
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def generate_fernet_key(key) -> bytes:
|
|
9
|
+
"""see cryptography.fernet.Fernet.generate_key()
|
|
10
|
+
|
|
11
|
+
:param key: _description_
|
|
12
|
+
:raises ValueError: _description_
|
|
13
|
+
:return: _description_
|
|
14
|
+
"""
|
|
15
|
+
if isinstance(key, str):
|
|
16
|
+
key = key.encode("utf-8")
|
|
17
|
+
if not isinstance(key, bytes):
|
|
18
|
+
raise ValueError
|
|
19
|
+
if len(key) < 32:
|
|
20
|
+
padder = padding.PKCS7(32 * 8).padder()
|
|
21
|
+
key = padder.update(key) + padder.finalize()
|
|
22
|
+
fernet_key = key[0:16] + key[16:32]
|
|
23
|
+
return base64.urlsafe_b64encode(fernet_key)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def fernet_encrypt(key, txt) -> str:
|
|
27
|
+
if isinstance(txt, str):
|
|
28
|
+
txt = txt.encode("utf-8")
|
|
29
|
+
fernet_key = generate_fernet_key(key)
|
|
30
|
+
f = Fernet(fernet_key)
|
|
31
|
+
token = f.encrypt(txt)
|
|
32
|
+
return token.decode("utf-8")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def fernet_decrypt(key, token) -> str:
|
|
36
|
+
if isinstance(token, str):
|
|
37
|
+
token = token.encode("utf-8")
|
|
38
|
+
fernet_key = generate_fernet_key(key)
|
|
39
|
+
f = Fernet(fernet_key)
|
|
40
|
+
decrypt_data = f.decrypt(token)
|
|
41
|
+
return decrypt_data.decode("utf-8")
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import os
|
|
3
|
+
import random
|
|
4
|
+
from cryptography.fernet import Fernet
|
|
5
|
+
from cryptography.hazmat.primitives import hashes
|
|
6
|
+
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
|
|
7
|
+
from ..random import rand_letter
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def bytes_to_url64str(bstr: bytes):
|
|
11
|
+
s = base64.urlsafe_b64encode(bstr).rstrip(b"=").decode()
|
|
12
|
+
return s
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def url64str_to_bytes(s):
|
|
16
|
+
_, r = divmod(len(s), 4)
|
|
17
|
+
bstr = base64.urlsafe_b64decode((s + "=" * r).encode())
|
|
18
|
+
return bstr
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def get_key(password, safe_salt=None, times=None):
|
|
22
|
+
if isinstance(password, str):
|
|
23
|
+
password = password.encode()
|
|
24
|
+
if safe_salt is None:
|
|
25
|
+
salt = os.urandom(16)
|
|
26
|
+
safe_salt = bytes_to_url64str(salt)
|
|
27
|
+
else:
|
|
28
|
+
salt = url64str_to_bytes(safe_salt)
|
|
29
|
+
if times is None:
|
|
30
|
+
times = random.randint(100, 100000)
|
|
31
|
+
|
|
32
|
+
kdf = PBKDF2HMAC(
|
|
33
|
+
algorithm=hashes.SHA256(),
|
|
34
|
+
length=32,
|
|
35
|
+
salt=salt,
|
|
36
|
+
iterations=times,
|
|
37
|
+
)
|
|
38
|
+
key = base64.urlsafe_b64encode(kdf.derive(password))
|
|
39
|
+
return (key, safe_salt, times)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def str_randsplit_to_list(s, n1=10, n2=30):
|
|
43
|
+
r = []
|
|
44
|
+
while s:
|
|
45
|
+
if len(s) < 40:
|
|
46
|
+
r.append(s)
|
|
47
|
+
break
|
|
48
|
+
k = random.randint(n1, n2)
|
|
49
|
+
r.append(s[:k])
|
|
50
|
+
if k < len(s):
|
|
51
|
+
s = s[k:]
|
|
52
|
+
else:
|
|
53
|
+
s = ""
|
|
54
|
+
return r
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def encrypt_str_to_list(s: str, password=None, prefix=None):
|
|
58
|
+
if password is None:
|
|
59
|
+
password = rand_letter(random.randint(6, 16))
|
|
60
|
+
out_password = password
|
|
61
|
+
else:
|
|
62
|
+
out_password = "-"
|
|
63
|
+
if prefix is not None:
|
|
64
|
+
password = str(prefix) + password
|
|
65
|
+
|
|
66
|
+
key, safe_salt, times = get_key(password)
|
|
67
|
+
|
|
68
|
+
cipher_suite = Fernet(key)
|
|
69
|
+
encrypted_data = cipher_suite.encrypt(s.encode())
|
|
70
|
+
safe_data = bytes_to_url64str(encrypted_data)
|
|
71
|
+
|
|
72
|
+
list_out = [out_password, safe_salt, str(times)] + str_randsplit_to_list(safe_data)
|
|
73
|
+
|
|
74
|
+
return list_out
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def decrypt_list_to_str(list_in, password=None, prefix=None):
|
|
78
|
+
_password, safe_salt, _times, *_data = list_in
|
|
79
|
+
if password is None:
|
|
80
|
+
password = _password
|
|
81
|
+
else:
|
|
82
|
+
password = password
|
|
83
|
+
if prefix is not None:
|
|
84
|
+
password = str(prefix) + password
|
|
85
|
+
|
|
86
|
+
times = int(_times)
|
|
87
|
+
s = "".join(_data)
|
|
88
|
+
key, _, _ = get_key(password, safe_salt, times)
|
|
89
|
+
cipher_suite = Fernet(key)
|
|
90
|
+
decrypted_bytes = cipher_suite.decrypt(url64str_to_bytes(s))
|
|
91
|
+
return decrypted_bytes.decode()
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def pg_dump(dump_dir, username, dbname, tbname):
|
|
6
|
+
"""pg_dump
|
|
7
|
+
|
|
8
|
+
:param dir: dump dir
|
|
9
|
+
:param username: db user
|
|
10
|
+
:param dbname: db name
|
|
11
|
+
:param tbname: table name
|
|
12
|
+
"""
|
|
13
|
+
pgdump_cmd = (
|
|
14
|
+
f"dump_user={username};dump_db={dbname};dump_tb={tbname};dump_dir={dump_dir};"
|
|
15
|
+
"pg_dump -U ${dump_user} --no-password -d ${dump_db} -t ${dump_tb} | gzip > ${dump_dir}/${dump_db}.${dump_tb}-`date +'%Y%m%d'`.gz;"
|
|
16
|
+
"cp ${dump_dir}/${dump_db}.${dump_tb}-`date +'%Y%m%d'`.gz ${dump_dir}/${dump_db}.${dump_tb}.gz;"
|
|
17
|
+
)
|
|
18
|
+
if sys.platform == "linux":
|
|
19
|
+
r = subprocess.run(pgdump_cmd, shell=True)
|
|
20
|
+
else:
|
|
21
|
+
r = f"not pg_dump because sys.platform is {sys.platform}"
|
|
22
|
+
return r
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def remove_ndays_ago(dir, days=30):
|
|
26
|
+
remove_cmd = f"rm -f {dir}/*`date +'%Y%m%d' -d'-{days} days'`.gz"
|
|
27
|
+
if sys.platform == "linux":
|
|
28
|
+
r = subprocess.run(remove_cmd, shell=True)
|
|
29
|
+
else:
|
|
30
|
+
r = f"not remove files because sys.platform is {sys.platform}"
|
|
31
|
+
return r
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .hash import hash_file, hash_text
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import hashlib
|
|
2
|
+
import os.path
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def hash_file(filename, algorithm="sha1"):
|
|
6
|
+
if os.path.isfile(filename) is False:
|
|
7
|
+
raise Exception("File not found for hash operation")
|
|
8
|
+
|
|
9
|
+
sha_func = getattr(hashlib, algorithm)
|
|
10
|
+
sha_obj = sha_func()
|
|
11
|
+
|
|
12
|
+
with open(filename, "rb") as f:
|
|
13
|
+
chunk = 0
|
|
14
|
+
while chunk != b"":
|
|
15
|
+
chunk = f.read(1024)
|
|
16
|
+
sha_obj.update(chunk)
|
|
17
|
+
|
|
18
|
+
return sha_obj.hexdigest()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def hash_text(text, algorithm="sha1"):
|
|
22
|
+
# hashlib.md5(text).hexdigest() # simple
|
|
23
|
+
sha_func = getattr(hashlib, algorithm)
|
|
24
|
+
sha_obj = sha_func()
|
|
25
|
+
sha_obj.update(text)
|
|
26
|
+
return sha_obj.hexdigest()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .jwt import jwt_encode, jwt_decode
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import jwt
|
|
2
|
+
import datetime
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def jwt_encode(payload: dict, key: str, delta: int = None, algorithm="HS256"):
|
|
6
|
+
"""encode payload.
|
|
7
|
+
|
|
8
|
+
:param payload: _description_
|
|
9
|
+
:param key: _description_
|
|
10
|
+
:param delta: _description_, defaults to None
|
|
11
|
+
:param algorithm: _description_, defaults to "HS256"
|
|
12
|
+
:return: An encoded access token
|
|
13
|
+
"""
|
|
14
|
+
if delta and delta > 0:
|
|
15
|
+
exp = datetime.datetime.now(tz=datetime.timezone.utc) + datetime.timedelta(
|
|
16
|
+
seconds=delta
|
|
17
|
+
)
|
|
18
|
+
payload |= {"exp": exp}
|
|
19
|
+
|
|
20
|
+
token = jwt.encode(payload, key, algorithm)
|
|
21
|
+
return token
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def jwt_decode(encoded, key: str, algorithm="HS256"):
|
|
25
|
+
"""decode jwt."""
|
|
26
|
+
|
|
27
|
+
payload = jwt.decode(encoded, key, algorithm)
|
|
28
|
+
return payload
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
# try:
|
|
32
|
+
# ...
|
|
33
|
+
# except Exception as e:
|
|
34
|
+
# # import sys
|
|
35
|
+
# # print(sys.exc_info())
|
|
36
|
+
# # return None,str(e)
|
|
37
|
+
# return None
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
def update_obj(db, old_obj, new_data: dict, NewClass, force=None):
|
|
2
|
+
"""insert or update object
|
|
3
|
+
|
|
4
|
+
:param db: sqlalchemy db
|
|
5
|
+
:param old_obj: old object
|
|
6
|
+
:param new_data: new dict
|
|
7
|
+
:param NewClass: object class
|
|
8
|
+
:param force: update= update old object with new attribute
|
|
9
|
+
"""
|
|
10
|
+
# 1. old is not exist
|
|
11
|
+
if old_obj is None:
|
|
12
|
+
new_obj = NewClass()
|
|
13
|
+
for k in new_data:
|
|
14
|
+
if hasattr(new_obj, k):
|
|
15
|
+
setattr(new_obj, k, new_data.get(k))
|
|
16
|
+
db.session.add(new_obj)
|
|
17
|
+
db.session.commit()
|
|
18
|
+
print(f"{new_obj} inserted")
|
|
19
|
+
return
|
|
20
|
+
|
|
21
|
+
# 2. old is exist
|
|
22
|
+
match force:
|
|
23
|
+
case None:
|
|
24
|
+
print(f"{old_obj} exists")
|
|
25
|
+
case "update":
|
|
26
|
+
# update old
|
|
27
|
+
for k in new_data:
|
|
28
|
+
if hasattr(old_obj, k):
|
|
29
|
+
setattr(old_obj, k, new_data.get(k))
|
|
30
|
+
db.session.commit()
|
|
31
|
+
print(f"{old_obj} updated")
|
|
32
|
+
case _:
|
|
33
|
+
raise Exception
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
import os.path
|
|
3
|
+
import json
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def run_process_in_venv(dir: str, cmd: str, params):
|
|
7
|
+
if not (os.path.exists(dir) and os.path.isdir(dir)):
|
|
8
|
+
return f"dir {dir} not exists"
|
|
9
|
+
|
|
10
|
+
cmd = [
|
|
11
|
+
os.path.join(dir, "venv/bin/python"),
|
|
12
|
+
os.path.join(dir, cmd),
|
|
13
|
+
json.dumps(params) if isinstance(params, dict) else str(params),
|
|
14
|
+
]
|
|
15
|
+
# print(cmd)
|
|
16
|
+
completedProcess = subprocess.run(cmd, capture_output=True)
|
|
17
|
+
r = str(completedProcess)
|
|
18
|
+
return r
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
import json
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def run_process(cmd: str, params=None,shell=False):
|
|
6
|
+
cmd_args = [
|
|
7
|
+
cmd,
|
|
8
|
+
json.dumps(params) if isinstance(params, dict) else str(params),
|
|
9
|
+
]
|
|
10
|
+
completedProcess = subprocess.run(cmd_args, capture_output=True,shell=shell)
|
|
11
|
+
r = str(completedProcess)
|
|
12
|
+
return r
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import random
|
|
2
|
+
import string
|
|
3
|
+
import os
|
|
4
|
+
import base64
|
|
5
|
+
import uuid
|
|
6
|
+
import secrets
|
|
7
|
+
|
|
8
|
+
# random.sample # unique
|
|
9
|
+
# random.choices # not unique
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def rand_digit(n):
|
|
13
|
+
"""随机数字"""
|
|
14
|
+
return "".join(random.choices(string.digits, k=n))
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def rand_letter(n: int):
|
|
18
|
+
return "".join(random.choices(string.ascii_letters + string.digits, k=n))
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def rand_sentence(n):
|
|
22
|
+
return "".join(
|
|
23
|
+
random.choices(string.ascii_letters + string.digits + " " * 10, k=n)
|
|
24
|
+
).strip()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def rand_uuid4():
|
|
28
|
+
return str(uuid.uuid4())
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def rand_token():
|
|
32
|
+
return uuid.uuid4().hex
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def secret_token():
|
|
36
|
+
return secrets.token_hex(32)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def secret_token_16():
|
|
40
|
+
return secrets.token_hex(16)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def rand_token_2(n):
|
|
44
|
+
# replace '+/' with '1a'
|
|
45
|
+
token = base64.b64encode(os.urandom(n), b"1a").decode()[0:n]
|
|
46
|
+
return token
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import shutil
|
|
3
|
+
|
|
4
|
+
def remove_pycache(dir_path):
|
|
5
|
+
for root, dirs, files in os.walk(dir_path):
|
|
6
|
+
if "venv" in root or "git" in root:
|
|
7
|
+
continue
|
|
8
|
+
for dir in dirs:
|
|
9
|
+
if dir == "__pycache__":
|
|
10
|
+
pycache_path = os.path.join(root, dir)
|
|
11
|
+
print(f"Removing {pycache_path}")
|
|
12
|
+
shutil.rmtree(pycache_path)
|
|
13
|
+
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
import random
|
|
3
|
+
import base64
|
|
4
|
+
from python_plugins.random import rand_letter, rand_sentence
|
|
5
|
+
from python_plugins.random import secret_token, secret_token_16
|
|
6
|
+
from python_plugins.crypto.str_to_list import get_key
|
|
7
|
+
from python_plugins.crypto.str_to_list import encrypt_str_to_list, decrypt_list_to_str
|
|
8
|
+
from python_plugins.crypto import generate_fernet_key, fernet_encrypt, fernet_decrypt
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class TestFetnet:
|
|
12
|
+
def test_random_secret_token(self):
|
|
13
|
+
token_1 = secret_token()
|
|
14
|
+
# print(token_1)
|
|
15
|
+
assert len(token_1) == 64
|
|
16
|
+
token_2 = secret_token_16()
|
|
17
|
+
# print(token_2)
|
|
18
|
+
assert len(token_2) == 32
|
|
19
|
+
|
|
20
|
+
def test_generate_fernet_key(self, fake):
|
|
21
|
+
skey = fake.sentence()
|
|
22
|
+
key = generate_fernet_key(skey)
|
|
23
|
+
# print(key)
|
|
24
|
+
decode_key = base64.urlsafe_b64decode(key)
|
|
25
|
+
# print(decode_key)
|
|
26
|
+
assert len(decode_key) == 32
|
|
27
|
+
|
|
28
|
+
def test_fernet_encrypt_decrypt(self, fake):
|
|
29
|
+
key = generate_fernet_key(fake.sentence())
|
|
30
|
+
txt = fake.sentence()
|
|
31
|
+
# print(key,txt)
|
|
32
|
+
token = fernet_encrypt(key, txt)
|
|
33
|
+
# print(token)
|
|
34
|
+
assert isinstance(token, str)
|
|
35
|
+
decrypt_txt = fernet_decrypt(key, token)
|
|
36
|
+
# print(decrypt_txt)
|
|
37
|
+
assert isinstance(decrypt_txt, str)
|
|
38
|
+
assert txt == decrypt_txt
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class TestStr2List:
|
|
42
|
+
def test_get_key(self):
|
|
43
|
+
password = rand_letter(random.randint(1, 50))
|
|
44
|
+
key, safe_salt, times = get_key(password)
|
|
45
|
+
decode_key = base64.urlsafe_b64decode(key)
|
|
46
|
+
assert len(decode_key) == 32
|
|
47
|
+
|
|
48
|
+
def test_encrypt_decrypt(self):
|
|
49
|
+
s = rand_sentence(random.randint(10, 100))
|
|
50
|
+
encrypted_list = encrypt_str_to_list(s)
|
|
51
|
+
s2 = decrypt_list_to_str(encrypted_list)
|
|
52
|
+
assert s == s2
|
|
53
|
+
|
|
54
|
+
def test_encrypt_decrypt_with_password(self):
|
|
55
|
+
s = rand_sentence(random.randint(10, 100))
|
|
56
|
+
password = rand_letter(16)
|
|
57
|
+
encrypted_list = encrypt_str_to_list(s, password)
|
|
58
|
+
with pytest.raises(Exception):
|
|
59
|
+
s1 = decrypt_list_to_str(encrypted_list)
|
|
60
|
+
s2 = decrypt_list_to_str(encrypted_list, password)
|
|
61
|
+
assert s == s2
|
|
62
|
+
s3 = decrypt_list_to_str([None] + encrypted_list[1:], password)
|
|
63
|
+
assert s == s3
|
|
64
|
+
|
|
65
|
+
def test_encrypt_decrypt_with_prefix(self):
|
|
66
|
+
s = rand_sentence(random.randint(10, 100))
|
|
67
|
+
prefix = rand_letter(16)
|
|
68
|
+
encrypted_list = encrypt_str_to_list(s, prefix=prefix)
|
|
69
|
+
with pytest.raises(Exception):
|
|
70
|
+
s1 = decrypt_list_to_str(encrypted_list)
|
|
71
|
+
s2 = decrypt_list_to_str(encrypted_list, prefix=prefix)
|
|
72
|
+
assert s == s2
|
|
73
|
+
|
|
74
|
+
def test_encrypt_decrypt_with_password_prefix(self):
|
|
75
|
+
s = rand_sentence(random.randint(10, 100))
|
|
76
|
+
password = rand_letter(16)
|
|
77
|
+
prefix = rand_letter(16)
|
|
78
|
+
encrypted_list = encrypt_str_to_list(s, password, prefix)
|
|
79
|
+
with pytest.raises(Exception):
|
|
80
|
+
s1 = decrypt_list_to_str(encrypted_list, prefix=prefix)
|
|
81
|
+
s2 = decrypt_list_to_str(encrypted_list, password, prefix)
|
|
82
|
+
assert s == s2
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import time
|
|
2
|
+
import datetime
|
|
3
|
+
from python_plugins.jwt import jwt_encode, jwt_decode
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def test_jwt(fake):
|
|
7
|
+
payload = {"some": fake.sentence()}
|
|
8
|
+
key = fake.vin()
|
|
9
|
+
# print(payload, key)
|
|
10
|
+
token = jwt_encode(payload, key)
|
|
11
|
+
# print(token)
|
|
12
|
+
decoded = jwt_decode(token, key)
|
|
13
|
+
# print(decoded)
|
|
14
|
+
assert decoded["some"] == payload["some"]
|
|
15
|
+
delta = 100
|
|
16
|
+
token = jwt_encode(payload, key, delta)
|
|
17
|
+
# print(token)
|
|
18
|
+
decoded = jwt_decode(token, key)
|
|
19
|
+
# print(decoded)
|
|
20
|
+
assert decoded["some"] == payload["some"]
|
|
21
|
+
# print(decoded["exp"],time.mktime(datetime.datetime.now().timetuple()))
|
|
22
|
+
assert "exp" in decoded
|
|
23
|
+
exp_time = time.mktime(datetime.datetime.now().timetuple()) + delta - 10
|
|
24
|
+
assert decoded["exp"] > exp_time
|
|
25
|
+
# print(datetime.datetime.fromtimestamp(decoded["exp"]))
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from sqlalchemy.orm import Mapped
|
|
2
|
+
from sqlalchemy.orm import mapped_column
|
|
3
|
+
from python_plugins.models.mixins import PrimaryKeyMixin
|
|
4
|
+
from python_plugins.models.mixins import DataMixin
|
|
5
|
+
from python_plugins.models.mixins import TimestampMixin
|
|
6
|
+
from sqlalchemy.orm import DeclarativeBase
|
|
7
|
+
from sqlalchemy.schema import CreateTable
|
|
8
|
+
|
|
9
|
+
from sqlalchemy import create_mock_engine
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Base(DeclarativeBase):
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Demo(PrimaryKeyMixin, DataMixin, TimestampMixin,Base):
|
|
17
|
+
__tablename__ = "demo"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def test_createtable():
|
|
21
|
+
|
|
22
|
+
assert Demo.__table__.name == "demo"
|
|
23
|
+
stmt = CreateTable(Demo.__table__)
|
|
24
|
+
# print(stmt)
|
|
25
|
+
assert "id INTEGER" in str(stmt)
|
|
26
|
+
assert "PRIMARY KEY" in str(stmt)
|
|
27
|
+
assert "data JSON" in str(stmt)
|
|
28
|
+
assert "created_at DATETIME" in str(stmt)
|
|
29
|
+
assert "updated_at DATETIME" in str(stmt)
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.1.2"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_plugins-0.1.2 → python_plugins-0.1.4}/src/python_plugins/models/mixins/data_mixin.py
RENAMED
|
File without changes
|
{python_plugins-0.1.2 → python_plugins-0.1.4}/src/python_plugins/models/mixins/primary_key_mixin.py
RENAMED
|
File without changes
|
{python_plugins-0.1.2 → python_plugins-0.1.4}/src/python_plugins/models/mixins/timestamp_mixin.py
RENAMED
|
File without changes
|
{python_plugins-0.1.2 → python_plugins-0.1.4}/src/python_plugins/models/mixins/user_minxin.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|