qsu 0.1.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.
- qsu/__init__.py +56 -0
- qsu/array/__init__.py +29 -0
- qsu/array/arrCount.py +8 -0
- qsu/array/arrGroupByMaxCount.py +15 -0
- qsu/array/arrMove.py +9 -0
- qsu/array/arrPick.py +8 -0
- qsu/array/arrRepeat.py +17 -0
- qsu/array/arrShuffle.py +15 -0
- qsu/array/arrTo1dArray.py +18 -0
- qsu/array/arrUnique.py +28 -0
- qsu/array/arrWithDefault.py +5 -0
- qsu/array/arrWithNumber.py +5 -0
- qsu/array/average.py +2 -0
- qsu/array/sortByObjectKey.py +20 -0
- qsu/array/sortNumeric.py +16 -0
- qsu/crypto/__init__.py +23 -0
- qsu/crypto/decodeBase64.py +5 -0
- qsu/crypto/decrypt.py +34 -0
- qsu/crypto/encodeBase64.py +5 -0
- qsu/crypto/encrypt.py +35 -0
- qsu/crypto/md5Hash.py +19 -0
- qsu/crypto/numberHash.py +14 -0
- qsu/crypto/objectId.py +9 -0
- qsu/crypto/sha1Hash.py +19 -0
- qsu/crypto/sha256Hash.py +19 -0
- qsu/crypto/sha512Hash.py +19 -0
- qsu/date/__init__.py +13 -0
- qsu/date/createDateListFromRange.py +55 -0
- qsu/date/dateToYYYYMMDD.py +20 -0
- qsu/date/dayDiff.py +10 -0
- qsu/date/isValidDate.py +27 -0
- qsu/date/today.py +19 -0
- qsu/file/__init__.py +51 -0
- qsu/file/_readLines.py +17 -0
- qsu/file/createDirectory.py +13 -0
- qsu/file/createFile.py +17 -0
- qsu/file/createFileWithDummy.py +21 -0
- qsu/file/deleteAllFileFromDirectory.py +15 -0
- qsu/file/deleteFile.py +16 -0
- qsu/file/getCopyFileName.py +21 -0
- qsu/file/getFileExtension.py +8 -0
- qsu/file/getFileHashFromPath.py +14 -0
- qsu/file/getFileHashFromStream.py +21 -0
- qsu/file/getFileInfo.py +42 -0
- qsu/file/getFileName.py +26 -0
- qsu/file/getFilePathLevel.py +12 -0
- qsu/file/getFileSize.py +10 -0
- qsu/file/getParentFilePath.py +10 -0
- qsu/file/headFile.py +19 -0
- qsu/file/isFileExists.py +9 -0
- qsu/file/isFileHidden.py +27 -0
- qsu/file/isValidFileName.py +14 -0
- qsu/file/joinFilePath.py +41 -0
- qsu/file/moveFile.py +8 -0
- qsu/file/normalizeFile.py +13 -0
- qsu/file/tailFile.py +24 -0
- qsu/file/toPosixFilePath.py +9 -0
- qsu/file/toValidFilePath.py +33 -0
- qsu/format/__init__.py +13 -0
- qsu/format/duration.py +45 -0
- qsu/format/fileSizeFormat.py +30 -0
- qsu/format/numberFormat.py +23 -0
- qsu/format/safeJSONParse.py +21 -0
- qsu/format/safeParseInt.py +39 -0
- qsu/math/__init__.py +15 -0
- qsu/math/div.py +9 -0
- qsu/math/mul.py +9 -0
- qsu/math/numPick.py +13 -0
- qsu/math/numUnique.py +10 -0
- qsu/math/sub.py +9 -0
- qsu/math/sum.py +9 -0
- qsu/misc/__init__.py +11 -0
- qsu/misc/debounce.py +14 -0
- qsu/misc/funcTimes.py +10 -0
- qsu/misc/logBox.py +174 -0
- qsu/misc/sleep.py +7 -0
- qsu/net/__init__.py +5 -0
- qsu/net/fetchData.py +186 -0
- qsu/object/__init__.py +19 -0
- qsu/object/objDeleteKeyByValue.py +29 -0
- qsu/object/objFindItemRecursiveByKey.py +25 -0
- qsu/object/objMergeNewKey.py +36 -0
- qsu/object/objTo1d.py +25 -0
- qsu/object/objToArray.py +16 -0
- qsu/object/objToPrettyStr.py +5 -0
- qsu/object/objToQueryString.py +27 -0
- qsu/object/objUpdate.py +25 -0
- qsu/os/__init__.py +17 -0
- qsu/os/getCpu.py +7 -0
- qsu/os/getHostname.py +28 -0
- qsu/os/getMachineId.py +33 -0
- qsu/os/getRamSize.py +34 -0
- qsu/os/getSid.py +60 -0
- qsu/os/getUptime.py +19 -0
- qsu/os/runCommand.py +28 -0
- qsu/string/__init__.py +41 -0
- qsu/string/capitalizeEachWords.py +43 -0
- qsu/string/capitalizeEverySentence.py +23 -0
- qsu/string/capitalizeFirst.py +5 -0
- qsu/string/getGroupKeys.py +103 -0
- qsu/string/getStrBytes.py +5 -0
- qsu/string/removeNewLine.py +8 -0
- qsu/string/removeSpecialChar.py +16 -0
- qsu/string/replaceBetween.py +15 -0
- qsu/string/split.py +45 -0
- qsu/string/strBlindRandom.py +26 -0
- qsu/string/strCount.py +12 -0
- qsu/string/strRandom.py +14 -0
- qsu/string/strShuffle.py +11 -0
- qsu/string/strToAscii.py +7 -0
- qsu/string/strUnique.py +5 -0
- qsu/string/trim.py +8 -0
- qsu/string/truncate.py +10 -0
- qsu/string/truncateExpect.py +25 -0
- qsu/string/urlJoin.py +27 -0
- qsu/verify/__init__.py +25 -0
- qsu/verify/between.py +7 -0
- qsu/verify/contains.py +12 -0
- qsu/verify/is2dArray.py +2 -0
- qsu/verify/isEmail.py +15 -0
- qsu/verify/isEmpty.py +12 -0
- qsu/verify/isEqual.py +32 -0
- qsu/verify/isEqualStrict.py +21 -0
- qsu/verify/isObject.py +2 -0
- qsu/verify/isTrueMinimumNumberOfTimes.py +8 -0
- qsu/verify/isUrl.py +20 -0
- qsu/verify/len.py +21 -0
- qsu/web/__init__.py +13 -0
- qsu/web/generateLicense.py +48 -0
- qsu/web/isBotAgent.py +10 -0
- qsu/web/isMatchPathname.py +24 -0
- qsu/web/isMobile.py +17 -0
- qsu/web/removeLocalePrefix.py +40 -0
- qsu-0.1.0.dist-info/METADATA +77 -0
- qsu-0.1.0.dist-info/RECORD +137 -0
- qsu-0.1.0.dist-info/WHEEL +4 -0
- qsu-0.1.0.dist-info/licenses/LICENSE +21 -0
qsu/__init__.py
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""qsu - Quick & Simple Utility.
|
|
2
|
+
|
|
3
|
+
A utility library that collects frequently used functions.
|
|
4
|
+
This package mirrors the JavaScript implementation of qsu.
|
|
5
|
+
|
|
6
|
+
Unlike the JavaScript package (where filesystem/OS/network/crypto helpers live
|
|
7
|
+
under `qsu/node`), Python has no browser/runtime split, so every function is
|
|
8
|
+
importable directly from the top-level `qsu` package.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from . import (
|
|
12
|
+
array,
|
|
13
|
+
crypto,
|
|
14
|
+
date,
|
|
15
|
+
file,
|
|
16
|
+
format,
|
|
17
|
+
math,
|
|
18
|
+
misc,
|
|
19
|
+
net,
|
|
20
|
+
object,
|
|
21
|
+
os,
|
|
22
|
+
string,
|
|
23
|
+
verify,
|
|
24
|
+
web,
|
|
25
|
+
)
|
|
26
|
+
from .array import * # noqa: F401,F403
|
|
27
|
+
from .crypto import * # noqa: F401,F403
|
|
28
|
+
from .date import * # noqa: F401,F403
|
|
29
|
+
from .file import * # noqa: F401,F403
|
|
30
|
+
from .format import * # noqa: F401,F403
|
|
31
|
+
from .math import * # noqa: F401,F403
|
|
32
|
+
from .misc import * # noqa: F401,F403
|
|
33
|
+
from .net import * # noqa: F401,F403
|
|
34
|
+
from .object import * # noqa: F401,F403
|
|
35
|
+
from .os import * # noqa: F401,F403
|
|
36
|
+
from .string import * # noqa: F401,F403
|
|
37
|
+
from .verify import * # noqa: F401,F403
|
|
38
|
+
from .web import * # noqa: F401,F403
|
|
39
|
+
|
|
40
|
+
__version__ = '0.1.0'
|
|
41
|
+
|
|
42
|
+
__all__ = [
|
|
43
|
+
*array.__all__,
|
|
44
|
+
*crypto.__all__,
|
|
45
|
+
*date.__all__,
|
|
46
|
+
*file.__all__,
|
|
47
|
+
*format.__all__,
|
|
48
|
+
*math.__all__,
|
|
49
|
+
*misc.__all__,
|
|
50
|
+
*net.__all__,
|
|
51
|
+
*object.__all__,
|
|
52
|
+
*os.__all__,
|
|
53
|
+
*string.__all__,
|
|
54
|
+
*verify.__all__,
|
|
55
|
+
*web.__all__,
|
|
56
|
+
]
|
qsu/array/__init__.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from .arrCount import arrCount
|
|
2
|
+
from .arrGroupByMaxCount import arrGroupByMaxCount
|
|
3
|
+
from .arrMove import arrMove
|
|
4
|
+
from .arrPick import arrPick
|
|
5
|
+
from .arrRepeat import arrRepeat
|
|
6
|
+
from .arrShuffle import arrShuffle
|
|
7
|
+
from .arrTo1dArray import arrTo1dArray
|
|
8
|
+
from .arrUnique import arrUnique
|
|
9
|
+
from .arrWithDefault import arrWithDefault
|
|
10
|
+
from .arrWithNumber import arrWithNumber
|
|
11
|
+
from .average import average
|
|
12
|
+
from .sortByObjectKey import sortByObjectKey
|
|
13
|
+
from .sortNumeric import sortNumeric
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
'arrCount',
|
|
17
|
+
'arrGroupByMaxCount',
|
|
18
|
+
'arrMove',
|
|
19
|
+
'arrPick',
|
|
20
|
+
'arrRepeat',
|
|
21
|
+
'arrShuffle',
|
|
22
|
+
'arrTo1dArray',
|
|
23
|
+
'arrUnique',
|
|
24
|
+
'arrWithDefault',
|
|
25
|
+
'arrWithNumber',
|
|
26
|
+
'average',
|
|
27
|
+
'sortByObjectKey',
|
|
28
|
+
'sortNumeric',
|
|
29
|
+
]
|
qsu/array/arrCount.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
def arrGroupByMaxCount(array: list, maxLengthPerGroup: int = 1) -> list:
|
|
2
|
+
result = []
|
|
3
|
+
tempArray = []
|
|
4
|
+
|
|
5
|
+
for item in array:
|
|
6
|
+
if len(tempArray) == maxLengthPerGroup:
|
|
7
|
+
result.append(tempArray)
|
|
8
|
+
tempArray = []
|
|
9
|
+
|
|
10
|
+
tempArray.append(item)
|
|
11
|
+
|
|
12
|
+
if len(tempArray) > 0:
|
|
13
|
+
result.append(tempArray)
|
|
14
|
+
|
|
15
|
+
return result
|
qsu/array/arrMove.py
ADDED
qsu/array/arrPick.py
ADDED
qsu/array/arrRepeat.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from ..verify.isObject import isObject
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def arrRepeat(array, count: int) -> list:
|
|
5
|
+
if not array or count < 1 or not isinstance(array, (list, tuple, dict)):
|
|
6
|
+
return []
|
|
7
|
+
|
|
8
|
+
isObj = isObject(array)
|
|
9
|
+
result = []
|
|
10
|
+
|
|
11
|
+
for _ in range(count):
|
|
12
|
+
if isObj:
|
|
13
|
+
result.append(array)
|
|
14
|
+
else:
|
|
15
|
+
result.extend(array)
|
|
16
|
+
|
|
17
|
+
return result
|
qsu/array/arrShuffle.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import math
|
|
2
|
+
import random
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def arrShuffle(array: list):
|
|
6
|
+
if len(array) == 1:
|
|
7
|
+
return array[0]
|
|
8
|
+
|
|
9
|
+
newArray = array
|
|
10
|
+
|
|
11
|
+
for i in range(len(array) - 1, 0, -1):
|
|
12
|
+
j = math.floor(random.random() * (i + 1))
|
|
13
|
+
newArray[i], newArray[j] = array[j], array[i]
|
|
14
|
+
|
|
15
|
+
return newArray
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from ..verify.is2dArray import is2dArray
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def arrTo1dArray(array: list) -> list:
|
|
5
|
+
def convert1dArray(arr: list) -> list:
|
|
6
|
+
tempArr = []
|
|
7
|
+
|
|
8
|
+
for item in arr:
|
|
9
|
+
if not isinstance(item, (list, tuple)):
|
|
10
|
+
tempArr.append(item)
|
|
11
|
+
elif is2dArray(item):
|
|
12
|
+
tempArr.extend(convert1dArray(item))
|
|
13
|
+
else:
|
|
14
|
+
tempArr.extend(item)
|
|
15
|
+
|
|
16
|
+
return tempArr
|
|
17
|
+
|
|
18
|
+
return convert1dArray(array)
|
qsu/array/arrUnique.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
from ..verify.is2dArray import is2dArray
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def arrUnique(array: list) -> list:
|
|
7
|
+
if is2dArray(array):
|
|
8
|
+
seen = set()
|
|
9
|
+
result = []
|
|
10
|
+
|
|
11
|
+
for item in array:
|
|
12
|
+
key = json.dumps(item, separators=(',', ':'))
|
|
13
|
+
|
|
14
|
+
if key not in seen:
|
|
15
|
+
seen.add(key)
|
|
16
|
+
result.append(json.loads(key))
|
|
17
|
+
|
|
18
|
+
return result
|
|
19
|
+
|
|
20
|
+
seen = []
|
|
21
|
+
result = []
|
|
22
|
+
|
|
23
|
+
for item in array:
|
|
24
|
+
if not any(existing is item or (type(existing) is type(item) and existing == item) for existing in seen):
|
|
25
|
+
seen.append(item)
|
|
26
|
+
result.append(item)
|
|
27
|
+
|
|
28
|
+
return result
|
qsu/array/average.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from .sortNumeric import _naturalKey
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def sortByObjectKey(
|
|
5
|
+
array: list,
|
|
6
|
+
key: str,
|
|
7
|
+
descending: bool = False,
|
|
8
|
+
numerically: bool = False,
|
|
9
|
+
) -> list:
|
|
10
|
+
if numerically:
|
|
11
|
+
array.sort(key=lambda item: _naturalKey(item[key]))
|
|
12
|
+
|
|
13
|
+
if descending:
|
|
14
|
+
array.reverse()
|
|
15
|
+
|
|
16
|
+
return array
|
|
17
|
+
|
|
18
|
+
array.sort(key=lambda item: item[key], reverse=descending)
|
|
19
|
+
|
|
20
|
+
return array
|
qsu/array/sortNumeric.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import re
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def _naturalKey(value: str):
|
|
5
|
+
parts = re.split(r'(\d+)', str(value))
|
|
6
|
+
|
|
7
|
+
return [(0, int(part)) if part.isdigit() else (1, part) for part in parts if part != '']
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def sortNumeric(array: list, descending: bool = False) -> list:
|
|
11
|
+
array.sort(key=_naturalKey)
|
|
12
|
+
|
|
13
|
+
if descending:
|
|
14
|
+
array.reverse()
|
|
15
|
+
|
|
16
|
+
return array
|
qsu/crypto/__init__.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from .decodeBase64 import decodeBase64
|
|
2
|
+
from .decrypt import decrypt
|
|
3
|
+
from .encodeBase64 import encodeBase64
|
|
4
|
+
from .encrypt import encrypt
|
|
5
|
+
from .md5Hash import md5Hash
|
|
6
|
+
from .numberHash import numberHash
|
|
7
|
+
from .objectId import objectId
|
|
8
|
+
from .sha1Hash import sha1Hash
|
|
9
|
+
from .sha256Hash import sha256Hash
|
|
10
|
+
from .sha512Hash import sha512Hash
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
'decodeBase64',
|
|
14
|
+
'decrypt',
|
|
15
|
+
'encodeBase64',
|
|
16
|
+
'encrypt',
|
|
17
|
+
'md5Hash',
|
|
18
|
+
'numberHash',
|
|
19
|
+
'objectId',
|
|
20
|
+
'sha1Hash',
|
|
21
|
+
'sha256Hash',
|
|
22
|
+
'sha512Hash',
|
|
23
|
+
]
|
qsu/crypto/decrypt.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
|
|
3
|
+
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def decrypt(
|
|
7
|
+
str: str,
|
|
8
|
+
secret: str,
|
|
9
|
+
algorithm: str = 'aes-256-cbc',
|
|
10
|
+
toBase64: bool = False,
|
|
11
|
+
) -> str:
|
|
12
|
+
if not str or len(str) < 1:
|
|
13
|
+
return ''
|
|
14
|
+
|
|
15
|
+
arrStr = str.split(':')
|
|
16
|
+
iv_part = arrStr.pop(0)
|
|
17
|
+
enc_part = ':'.join(arrStr)
|
|
18
|
+
|
|
19
|
+
if toBase64:
|
|
20
|
+
iv = base64.b64decode(iv_part)
|
|
21
|
+
enc = base64.b64decode(enc_part)
|
|
22
|
+
else:
|
|
23
|
+
iv = bytes.fromhex(iv_part)
|
|
24
|
+
enc = bytes.fromhex(enc_part)
|
|
25
|
+
|
|
26
|
+
cipher = Cipher(algorithms.AES(secret.encode('utf-8')), modes.CBC(iv))
|
|
27
|
+
decryptor = cipher.decryptor()
|
|
28
|
+
decrypted = decryptor.update(enc) + decryptor.finalize()
|
|
29
|
+
|
|
30
|
+
# Remove PKCS7 padding.
|
|
31
|
+
pad_len = decrypted[-1]
|
|
32
|
+
decrypted = decrypted[:-pad_len]
|
|
33
|
+
|
|
34
|
+
return decrypted.decode('utf-8')
|
qsu/crypto/encrypt.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def encrypt(
|
|
8
|
+
str: str,
|
|
9
|
+
secret: str,
|
|
10
|
+
algorithm: str = 'aes-256-cbc',
|
|
11
|
+
ivSize: int = 16,
|
|
12
|
+
toBase64: bool = False,
|
|
13
|
+
) -> str:
|
|
14
|
+
if not str or len(str) < 1:
|
|
15
|
+
return ''
|
|
16
|
+
|
|
17
|
+
iv = os.urandom(ivSize)
|
|
18
|
+
cipher = Cipher(algorithms.AES(secret.encode('utf-8')), modes.CBC(iv))
|
|
19
|
+
encryptor = cipher.encryptor()
|
|
20
|
+
|
|
21
|
+
data = str.encode('utf-8')
|
|
22
|
+
block_size = 16
|
|
23
|
+
pad_len = block_size - (len(data) % block_size)
|
|
24
|
+
padded = data + bytes([pad_len]) * pad_len
|
|
25
|
+
|
|
26
|
+
enc = encryptor.update(padded) + encryptor.finalize()
|
|
27
|
+
|
|
28
|
+
if toBase64:
|
|
29
|
+
iv_str = base64.b64encode(iv).decode('ascii')
|
|
30
|
+
enc_str = base64.b64encode(enc).decode('ascii')
|
|
31
|
+
else:
|
|
32
|
+
iv_str = iv.hex()
|
|
33
|
+
enc_str = enc.hex()
|
|
34
|
+
|
|
35
|
+
return f'{iv_str}:{enc_str}'
|
qsu/crypto/md5Hash.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import hashlib
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def md5Hash(str: str, encoding: Optional[str] = None) -> str:
|
|
7
|
+
digest = hashlib.md5(str.encode('utf-8')).digest()
|
|
8
|
+
enc = encoding or 'hex'
|
|
9
|
+
|
|
10
|
+
if enc == 'hex':
|
|
11
|
+
return digest.hex()
|
|
12
|
+
if enc == 'base64':
|
|
13
|
+
return base64.b64encode(digest).decode('ascii')
|
|
14
|
+
if enc == 'base64url':
|
|
15
|
+
return base64.urlsafe_b64encode(digest).decode('ascii').rstrip('=')
|
|
16
|
+
if enc == 'binary':
|
|
17
|
+
return digest.decode('latin-1')
|
|
18
|
+
|
|
19
|
+
raise ValueError(f'Unsupported encoding: {encoding}')
|
qsu/crypto/numberHash.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
def numberHash(str: str) -> int:
|
|
2
|
+
if not str:
|
|
3
|
+
return 0
|
|
4
|
+
|
|
5
|
+
hash = 0
|
|
6
|
+
|
|
7
|
+
for ch in str:
|
|
8
|
+
hash = (hash << 5) - hash + ord(ch)
|
|
9
|
+
# Emulate JS `hash |= 0`: keep low 32 bits and interpret as signed.
|
|
10
|
+
hash &= 0xFFFFFFFF
|
|
11
|
+
if hash >= 0x80000000:
|
|
12
|
+
hash -= 0x100000000
|
|
13
|
+
|
|
14
|
+
return hash
|
qsu/crypto/objectId.py
ADDED
qsu/crypto/sha1Hash.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import hashlib
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def sha1Hash(str: str, encoding: Optional[str] = None) -> str:
|
|
7
|
+
digest = hashlib.sha1(str.encode('utf-8')).digest()
|
|
8
|
+
enc = encoding or 'hex'
|
|
9
|
+
|
|
10
|
+
if enc == 'hex':
|
|
11
|
+
return digest.hex()
|
|
12
|
+
if enc == 'base64':
|
|
13
|
+
return base64.b64encode(digest).decode('ascii')
|
|
14
|
+
if enc == 'base64url':
|
|
15
|
+
return base64.urlsafe_b64encode(digest).decode('ascii').rstrip('=')
|
|
16
|
+
if enc == 'binary':
|
|
17
|
+
return digest.decode('latin-1')
|
|
18
|
+
|
|
19
|
+
raise ValueError(f'Unsupported encoding: {encoding}')
|
qsu/crypto/sha256Hash.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import hashlib
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def sha256Hash(str: str, encoding: Optional[str] = None) -> str:
|
|
7
|
+
digest = hashlib.sha256(str.encode('utf-8')).digest()
|
|
8
|
+
enc = encoding or 'hex'
|
|
9
|
+
|
|
10
|
+
if enc == 'hex':
|
|
11
|
+
return digest.hex()
|
|
12
|
+
if enc == 'base64':
|
|
13
|
+
return base64.b64encode(digest).decode('ascii')
|
|
14
|
+
if enc == 'base64url':
|
|
15
|
+
return base64.urlsafe_b64encode(digest).decode('ascii').rstrip('=')
|
|
16
|
+
if enc == 'binary':
|
|
17
|
+
return digest.decode('latin-1')
|
|
18
|
+
|
|
19
|
+
raise ValueError(f'Unsupported encoding: {encoding}')
|
qsu/crypto/sha512Hash.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import hashlib
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def sha512Hash(str: str, encoding: Optional[str] = None) -> str:
|
|
7
|
+
digest = hashlib.sha512(str.encode('utf-8')).digest()
|
|
8
|
+
enc = encoding or 'hex'
|
|
9
|
+
|
|
10
|
+
if enc == 'hex':
|
|
11
|
+
return digest.hex()
|
|
12
|
+
if enc == 'base64':
|
|
13
|
+
return base64.b64encode(digest).decode('ascii')
|
|
14
|
+
if enc == 'base64url':
|
|
15
|
+
return base64.urlsafe_b64encode(digest).decode('ascii').rstrip('=')
|
|
16
|
+
if enc == 'binary':
|
|
17
|
+
return digest.decode('latin-1')
|
|
18
|
+
|
|
19
|
+
raise ValueError(f'Unsupported encoding: {encoding}')
|
qsu/date/__init__.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from .createDateListFromRange import createDateListFromRange
|
|
2
|
+
from .dateToYYYYMMDD import dateToYYYYMMDD
|
|
3
|
+
from .dayDiff import dayDiff
|
|
4
|
+
from .isValidDate import isValidDate
|
|
5
|
+
from .today import today
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
'createDateListFromRange',
|
|
9
|
+
'dateToYYYYMMDD',
|
|
10
|
+
'dayDiff',
|
|
11
|
+
'isValidDate',
|
|
12
|
+
'today',
|
|
13
|
+
]
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import math
|
|
2
|
+
import re
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
|
|
5
|
+
from .dateToYYYYMMDD import dateToYYYYMMDD
|
|
6
|
+
from .isValidDate import isValidDate
|
|
7
|
+
|
|
8
|
+
_YEAR_END_RE = re.compile(r'[0-9]{4}-12-31')
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def createDateListFromRange(startDate: datetime, endDate: datetime):
|
|
12
|
+
if not isValidDate(dateToYYYYMMDD(startDate)) or not isValidDate(
|
|
13
|
+
dateToYYYYMMDD(endDate)
|
|
14
|
+
):
|
|
15
|
+
raise ValueError('Either the start date or end date is an invalid date.')
|
|
16
|
+
|
|
17
|
+
date_diff = math.floor((endDate.timestamp() - startDate.timestamp()) / 86400)
|
|
18
|
+
|
|
19
|
+
if date_diff < 0:
|
|
20
|
+
raise ValueError('The start date is more recent than the end date.')
|
|
21
|
+
|
|
22
|
+
end_date_str = dateToYYYYMMDD(endDate)
|
|
23
|
+
all_date = []
|
|
24
|
+
current_year = startDate.year
|
|
25
|
+
current_month = startDate.month
|
|
26
|
+
current_day = startDate.day
|
|
27
|
+
current_date_str = ''
|
|
28
|
+
|
|
29
|
+
def create_new_date_str(year: int, month: int, day: int) -> str:
|
|
30
|
+
m = f'0{month}' if month < 10 else f'{month}'
|
|
31
|
+
d = f'0{day}' if day < 10 else f'{day}'
|
|
32
|
+
return f'{year}-{m}-{d}'
|
|
33
|
+
|
|
34
|
+
while end_date_str != current_date_str:
|
|
35
|
+
if _YEAR_END_RE.search(current_date_str):
|
|
36
|
+
current_year += 1
|
|
37
|
+
current_month = 1
|
|
38
|
+
current_day = 1
|
|
39
|
+
|
|
40
|
+
current_new_date_str = create_new_date_str(
|
|
41
|
+
current_year, current_month, current_day
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
if isValidDate(current_new_date_str):
|
|
45
|
+
current_day += 1
|
|
46
|
+
all_date.append(current_new_date_str)
|
|
47
|
+
current_date_str = current_new_date_str
|
|
48
|
+
else:
|
|
49
|
+
current_month += 1
|
|
50
|
+
current_day = 1
|
|
51
|
+
current_date_str = create_new_date_str(
|
|
52
|
+
current_year, current_month, current_day
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
return all_date
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from datetime import date as _date
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def dateToYYYYMMDD(date, separator: str = '-') -> str:
|
|
6
|
+
if isinstance(date, datetime):
|
|
7
|
+
year = date.year
|
|
8
|
+
month = date.month
|
|
9
|
+
day = date.day
|
|
10
|
+
elif isinstance(date, _date):
|
|
11
|
+
year = date.year
|
|
12
|
+
month = date.month
|
|
13
|
+
day = date.day
|
|
14
|
+
else:
|
|
15
|
+
raise TypeError('date must be a datetime or date instance')
|
|
16
|
+
|
|
17
|
+
month_str = f'0{month}' if month < 10 else f'{month}'
|
|
18
|
+
day_str = f'0{day}' if day < 10 else f'{day}'
|
|
19
|
+
|
|
20
|
+
return f'{year}{separator}{month_str}{separator}{day_str}'
|
qsu/date/dayDiff.py
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import math
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def dayDiff(date1: datetime, date2: datetime = None) -> int:
|
|
6
|
+
date2c = date2 if date2 is not None else datetime.now()
|
|
7
|
+
|
|
8
|
+
diff_ms = abs(date2c.timestamp() - date1.timestamp()) * 1000
|
|
9
|
+
|
|
10
|
+
return math.ceil(diff_ms / (1000 * 3600 * 24))
|
qsu/date/isValidDate.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import re
|
|
2
|
+
|
|
3
|
+
_FORMAT_RE = re.compile(r'^[0-9]{4}-[0-9]{2}-[0-9]{2}$')
|
|
4
|
+
|
|
5
|
+
# Support range: 1600-01-01 ~ 9999/12/31
|
|
6
|
+
_VALID_RE = re.compile(
|
|
7
|
+
r'^(?=\d)(?:(?:31(?!.(?:0?[2469]|11))|(?:30|29)(?!.0?2)|29(?=.0?2.(?:(?:1[6-9]|[2-9]\d)?'
|
|
8
|
+
r'(?:0[48]|[2468][048]|[13579][26])|(?:16|[2468][048]|[3579][26])00)(?:\x20|$))|'
|
|
9
|
+
r'(?:2[0-8]|1\d|0?[1-9]))([-./])(?:1[012]|0?[1-9])\1(?:1[6-9]|[2-9]\d)?\d\d'
|
|
10
|
+
r'(?:(?=\x20\d)\x20|$))?(((0?[1-9]|1[012])(:[0-5]\d){0,2}(\x20[AP]M))|'
|
|
11
|
+
r'([01]\d|2[0-3])(:[0-5]\d){1,2})?$'
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def isValidDate(dateYYYYMMDD: str) -> bool:
|
|
16
|
+
if not _FORMAT_RE.match(dateYYYYMMDD):
|
|
17
|
+
raise ValueError("The date format must be 'YYYY-MM-DD'")
|
|
18
|
+
|
|
19
|
+
converted_date = dateYYYYMMDD.split('-')
|
|
20
|
+
|
|
21
|
+
candidate = (
|
|
22
|
+
f'{int(converted_date[2], 10)}-'
|
|
23
|
+
f'{int(converted_date[1], 10)}-'
|
|
24
|
+
f'{int(converted_date[0], 10)}'
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
return bool(_VALID_RE.match(candidate))
|
qsu/date/today.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def today(separator: str = '-', yearFirst: bool = True) -> str:
|
|
5
|
+
date = datetime.now()
|
|
6
|
+
month = date.month
|
|
7
|
+
day = date.day
|
|
8
|
+
|
|
9
|
+
date_arr = [
|
|
10
|
+
f"{'0' if month < 10 else ''}{month}",
|
|
11
|
+
f"{'0' if day < 10 else ''}{day}",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
if yearFirst:
|
|
15
|
+
date_arr.insert(0, str(date.year))
|
|
16
|
+
else:
|
|
17
|
+
date_arr.append(str(date.year))
|
|
18
|
+
|
|
19
|
+
return separator.join(date_arr)
|