enconia-utils 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.
- enconia_utils/__init__.py +71 -0
- enconia_utils/aliyun_sls_log.py +133 -0
- enconia_utils/feishu_push.py +173 -0
- enconia_utils/id_generator.py +114 -0
- enconia_utils/util_date.py +187 -0
- enconia_utils/util_oss.py +98 -0
- enconia_utils/util_type_mapping.py +343 -0
- enconia_utils-0.1.0.dist-info/METADATA +76 -0
- enconia_utils-0.1.0.dist-info/RECORD +12 -0
- enconia_utils-0.1.0.dist-info/WHEEL +5 -0
- enconia_utils-0.1.0.dist-info/licenses/LICENSE +29 -0
- enconia_utils-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""工具函数集合"""
|
|
2
|
+
|
|
3
|
+
from .aliyun_sls_log import send_log_to_sls
|
|
4
|
+
from .feishu_push import FeiShuTalkChatBot, NotifyLevel
|
|
5
|
+
from .id_generator import (
|
|
6
|
+
get_random_short_id,
|
|
7
|
+
get_fix_short_id,
|
|
8
|
+
get_random_long_id,
|
|
9
|
+
get_fix_long_id,
|
|
10
|
+
get_random_uuid,
|
|
11
|
+
get_fix_uuid,
|
|
12
|
+
get_file_md5,
|
|
13
|
+
)
|
|
14
|
+
from .util_date import (
|
|
15
|
+
DateTimeFormat,
|
|
16
|
+
DateMaxMin,
|
|
17
|
+
DateEncoder,
|
|
18
|
+
get_orbit_std_datetime,
|
|
19
|
+
get_orbit_std_datetime_utc,
|
|
20
|
+
get_orbit_std_date,
|
|
21
|
+
get_orbit_std_date_utc,
|
|
22
|
+
get_date_range_by_base,
|
|
23
|
+
get_date_range_list_v1,
|
|
24
|
+
get_date_range_list_v2,
|
|
25
|
+
get_next_day,
|
|
26
|
+
get_next_workday_cn,
|
|
27
|
+
)
|
|
28
|
+
from .util_oss import OssCloudObjectProvider
|
|
29
|
+
from .util_type_mapping import (
|
|
30
|
+
ExtenCons,
|
|
31
|
+
content2file_type,
|
|
32
|
+
file2content_type,
|
|
33
|
+
get_content_type_4_filename,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
__version__ = "0.1.0"
|
|
37
|
+
__all__ = [
|
|
38
|
+
# aliyun_sls_log
|
|
39
|
+
"send_log_to_sls",
|
|
40
|
+
# feishu_push
|
|
41
|
+
"FeiShuTalkChatBot",
|
|
42
|
+
"NotifyLevel",
|
|
43
|
+
# id_generator
|
|
44
|
+
"get_random_short_id",
|
|
45
|
+
"get_fix_short_id",
|
|
46
|
+
"get_random_long_id",
|
|
47
|
+
"get_fix_long_id",
|
|
48
|
+
"get_random_uuid",
|
|
49
|
+
"get_fix_uuid",
|
|
50
|
+
"get_file_md5",
|
|
51
|
+
# util_date
|
|
52
|
+
"DateTimeFormat",
|
|
53
|
+
"DateMaxMin",
|
|
54
|
+
"DateEncoder",
|
|
55
|
+
"get_orbit_std_datetime",
|
|
56
|
+
"get_orbit_std_datetime_utc",
|
|
57
|
+
"get_orbit_std_date",
|
|
58
|
+
"get_orbit_std_date_utc",
|
|
59
|
+
"get_date_range_by_base",
|
|
60
|
+
"get_date_range_list_v1",
|
|
61
|
+
"get_date_range_list_v2",
|
|
62
|
+
"get_next_day",
|
|
63
|
+
"get_next_workday_cn",
|
|
64
|
+
# util_oss
|
|
65
|
+
"OssCloudObjectProvider",
|
|
66
|
+
# util_type_mapping
|
|
67
|
+
"ExtenCons",
|
|
68
|
+
"content2file_type",
|
|
69
|
+
"file2content_type",
|
|
70
|
+
"get_content_type_4_filename",
|
|
71
|
+
]
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"""
|
|
2
|
+
集成阿里云SLS日志服务
|
|
3
|
+
|
|
4
|
+
使用说明:
|
|
5
|
+
1. 安装依赖:
|
|
6
|
+
pip install aliyun-log-python-sdk
|
|
7
|
+
|
|
8
|
+
2. 配置阿里云SLS:
|
|
9
|
+
- 在阿里云控制台创建Project和Logstore
|
|
10
|
+
- 获取AccessKey ID和AccessKey Secret
|
|
11
|
+
- 设置环境变量或修改下方配置:
|
|
12
|
+
ALIBABA_CLOUD_ACCESS_KEY_ID=你的AccessKeyID
|
|
13
|
+
ALIBABA_CLOUD_ACCESS_KEY_SECRET=你的AccessKeySecret
|
|
14
|
+
|
|
15
|
+
3. 配置信息获取:
|
|
16
|
+
- Endpoint: 在SLS控制台查看,如 cn-hangzhou.log.aliyuncs.com
|
|
17
|
+
- Project: 在SLS控制台创建的项目名称
|
|
18
|
+
- Logstore: 在项目下创建的日志库名称
|
|
19
|
+
|
|
20
|
+
4. 验证配置:
|
|
21
|
+
- 运行脚本后查看SLS控制台是否收到日志
|
|
22
|
+
- 检查本地日志文件 logs/*.log
|
|
23
|
+
|
|
24
|
+
5. 查看日志:
|
|
25
|
+
- SLS控制台: https://sls.console.aliyun.com
|
|
26
|
+
- 本地日志: logs/*.log
|
|
27
|
+
|
|
28
|
+
当前实现:
|
|
29
|
+
- 使用阿里云官方SLS Python SDK写入日志
|
|
30
|
+
- 支持SDK和HTTP API两种发送方式(自动降级)
|
|
31
|
+
- 所有关键操作都会同时记录到本地日志和SLS
|
|
32
|
+
"""
|
|
33
|
+
import logging
|
|
34
|
+
|
|
35
|
+
import requests
|
|
36
|
+
import time
|
|
37
|
+
import os
|
|
38
|
+
from datetime import datetime
|
|
39
|
+
from aliyun.log import LogClient, LogItem, PutLogsRequest
|
|
40
|
+
|
|
41
|
+
# 阿里云SLS日志服务配置
|
|
42
|
+
# try:
|
|
43
|
+
# from aliyun.log import LogClient, LogItem, PutLogsRequest
|
|
44
|
+
# SLS_SDK_AVAILABLE = True
|
|
45
|
+
# except ImportError:
|
|
46
|
+
# SLS_SDK_AVAILABLE = False
|
|
47
|
+
# logging.warning("阿里云SLS SDK未安装,将使用HTTP API方式发送日志")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
# 阿里云SLS配置信息 - 请根据实际情况修改
|
|
51
|
+
SLS_SDK_AVAILABLE = True
|
|
52
|
+
SLS_ENDPOINT = 'cn-shanghai.log.aliyuncs.com' # 您的日志服务Endpoint
|
|
53
|
+
SLS_PROJECT = 'mineru-logs' # 您的Project名称
|
|
54
|
+
SLS_LOGSTORE = 'mineru-3log' # 您的Logstore名称
|
|
55
|
+
SLS_ACCESS_KEY_ID = os.environ.get('ALIBABA_CLOUD_ACCESS_KEY_ID', 'LTAI5tLtyiy18UAWL626k1wC')
|
|
56
|
+
SLS_ACCESS_KEY_SECRET = os.environ.get('ALIBABA_CLOUD_ACCESS_KEY_SECRET', 'T6VSV1QFn7hsc8wjrOGG8K4meDyXbX')
|
|
57
|
+
|
|
58
|
+
logger = logging.getLogger(__name__)
|
|
59
|
+
|
|
60
|
+
def send_log_to_sls(script_name,level, message, **kwargs):
|
|
61
|
+
"""发送日志到阿里云SLS"""
|
|
62
|
+
try:
|
|
63
|
+
if SLS_SDK_AVAILABLE:
|
|
64
|
+
# 使用SDK方式发送日志
|
|
65
|
+
client = LogClient(SLS_ENDPOINT, SLS_ACCESS_KEY_ID, SLS_ACCESS_KEY_SECRET)
|
|
66
|
+
|
|
67
|
+
# 创建日志项
|
|
68
|
+
log_item = LogItem()
|
|
69
|
+
log_item.set_time(int(time.time()))
|
|
70
|
+
|
|
71
|
+
# 添加日志内容
|
|
72
|
+
log_item.set_contents([
|
|
73
|
+
('level', level),
|
|
74
|
+
('message', message),
|
|
75
|
+
('timestamp', datetime.now().isoformat()),
|
|
76
|
+
('script', script_name),
|
|
77
|
+
('extra_info', str(kwargs) if kwargs else '')
|
|
78
|
+
])
|
|
79
|
+
|
|
80
|
+
# 构建请求
|
|
81
|
+
request = PutLogsRequest(SLS_PROJECT, SLS_LOGSTORE, '', '', [log_item])
|
|
82
|
+
|
|
83
|
+
# 发送日志
|
|
84
|
+
response = client.put_logs(request)
|
|
85
|
+
|
|
86
|
+
# put_logs在成功时不会抛出异常,如果失败会抛出异常
|
|
87
|
+
logger.info(f"SLS日志发送成功: {message}")
|
|
88
|
+
|
|
89
|
+
else:
|
|
90
|
+
# 使用HTTP API方式发送日志
|
|
91
|
+
send_log_via_http(script_name,level, message, **kwargs)
|
|
92
|
+
|
|
93
|
+
except Exception as e:
|
|
94
|
+
logger.error(f"SLS日志发送异常: {str(e)}")
|
|
95
|
+
# 如果SDK方式失败,尝试使用HTTP方式
|
|
96
|
+
try:
|
|
97
|
+
send_log_via_http(script_name,level, message, **kwargs)
|
|
98
|
+
except Exception as http_e:
|
|
99
|
+
logger.error(f"HTTP方式也发送失败: {str(http_e)}")
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def send_log_via_http(script_name, level, message, **kwargs):
|
|
103
|
+
"""通过HTTP API发送日志到SLS"""
|
|
104
|
+
try:
|
|
105
|
+
|
|
106
|
+
# 构建日志数据
|
|
107
|
+
log_data = {
|
|
108
|
+
'level': level,
|
|
109
|
+
'message': message,
|
|
110
|
+
'timestamp': datetime.now().isoformat(),
|
|
111
|
+
'script': script_name,
|
|
112
|
+
'extra_info': kwargs if kwargs else {}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
# 构建请求头
|
|
116
|
+
headers = {
|
|
117
|
+
'Content-Type': 'application/json',
|
|
118
|
+
'x-log-apiversion': '0.6.0',
|
|
119
|
+
'x-log-signaturemethod': 'hmac-sha1'
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
# 发送HTTP请求到SLS API
|
|
123
|
+
url = f"https://{SLS_ENDPOINT}/logstores/{SLS_LOGSTORE}/shards/lb"
|
|
124
|
+
|
|
125
|
+
response = requests.post(url, headers=headers, json=log_data, timeout=5)
|
|
126
|
+
|
|
127
|
+
if response.status_code == 200:
|
|
128
|
+
logger.info(f"HTTP方式SLS日志发送成功: {message}")
|
|
129
|
+
else:
|
|
130
|
+
logger.error(f"HTTP方式SLS日志发送失败: 状态码={response.status_code}, 响应={response.text[:200]}")
|
|
131
|
+
|
|
132
|
+
except Exception as e:
|
|
133
|
+
logger.error(f"HTTP方式SLS日志发送异常: {str(e)}")
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
import json
|
|
3
|
+
import time
|
|
4
|
+
import logging
|
|
5
|
+
from enum import Enum
|
|
6
|
+
from typing import Optional, List, Dict
|
|
7
|
+
|
|
8
|
+
try:
|
|
9
|
+
JSONDecodeError = json.decoder.JSONDecodeError
|
|
10
|
+
except AttributeError:
|
|
11
|
+
JSONDecodeError = ValueError
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class NotifyLevel(Enum):
|
|
15
|
+
SUCCESS = 'green'
|
|
16
|
+
INFO = 'wathet'
|
|
17
|
+
WARNING = 'yellow'
|
|
18
|
+
DANGER = 'red'
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class FeiShuTalkChatBot:
|
|
22
|
+
def __init__(self, webhook, secret=None, pc_slide=False, fail_notice=False):
|
|
23
|
+
'''
|
|
24
|
+
机器人初始化
|
|
25
|
+
Reference doc: https://open.feishu.cn/document/client-docs/bot-v3/add-custom-bot
|
|
26
|
+
|
|
27
|
+
:param webhook: 飞书群自定义机器人webhook地址
|
|
28
|
+
:param secret: 机器人安全设置页面勾选“加签”时需要传入的密钥
|
|
29
|
+
:param pc_slide: 消息链接打开方式,默认False为浏览器打开,设置为True时为PC端侧边栏打开
|
|
30
|
+
:param fail_notice: 消息发送失败提醒,默认为False不提醒,开发者可以根据返回的消息发送结果自行判断和处理
|
|
31
|
+
'''
|
|
32
|
+
super(FeiShuTalkChatBot, self).__init__()
|
|
33
|
+
self.headers = {'Content-Type': 'application/json; charset=utf-8'}
|
|
34
|
+
self.webhook = webhook
|
|
35
|
+
self.secret = secret
|
|
36
|
+
self.pc_slide = pc_slide
|
|
37
|
+
self.fail_notice = fail_notice
|
|
38
|
+
|
|
39
|
+
def success(self, message, title: str = 'Successfully', ats: Optional[List] = None, action=None):
|
|
40
|
+
self.send_text(title=title, message=message, level=NotifyLevel.SUCCESS.value, ats=ats, action=action)
|
|
41
|
+
|
|
42
|
+
def info(self, message, title: str = 'Notification', ats: Optional[List] = None, action=None):
|
|
43
|
+
self.send_text(title=title, message=message, level=NotifyLevel.INFO.value, ats=ats, action=action)
|
|
44
|
+
|
|
45
|
+
def warning(self, message, title: str = 'Warning', ats: Optional[List] = None, action=None):
|
|
46
|
+
self.send_text(title=title, message=message, level=NotifyLevel.WARNING.value, ats=ats, action=action)
|
|
47
|
+
|
|
48
|
+
def error(self, message, title: str = 'Error', ats: Optional[List] = None, action=None):
|
|
49
|
+
self.send_text(title=title, message=message, level=NotifyLevel.DANGER.value, ats=ats, action=action)
|
|
50
|
+
|
|
51
|
+
def send_text(self, title, message, level, ats: Optional[List] = None, action: Optional[Dict] = None):
|
|
52
|
+
"""
|
|
53
|
+
:param title:
|
|
54
|
+
:param message:
|
|
55
|
+
:param level:
|
|
56
|
+
:param ats:
|
|
57
|
+
:param action: { "direction": "DOWN", actions: {} }
|
|
58
|
+
:return:
|
|
59
|
+
"""
|
|
60
|
+
data = {
|
|
61
|
+
"msg_type": "interactive",
|
|
62
|
+
"card": {
|
|
63
|
+
"elements": [
|
|
64
|
+
{
|
|
65
|
+
"tag": "hr"
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"tag": "div",
|
|
69
|
+
"text": {
|
|
70
|
+
"tag": "lark_md",
|
|
71
|
+
"content": message
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
],
|
|
75
|
+
"header": {
|
|
76
|
+
"title": {
|
|
77
|
+
"content": title,
|
|
78
|
+
"tag": "plain_text"
|
|
79
|
+
},
|
|
80
|
+
"template": level
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
# Put @ people
|
|
86
|
+
if ats:
|
|
87
|
+
if len(ats) == 1 and ats[0] == "all":
|
|
88
|
+
data["card"]["elements"].append({
|
|
89
|
+
"tag": "div",
|
|
90
|
+
"text": {
|
|
91
|
+
"tag": "lark_md",
|
|
92
|
+
"content": "At <at id=all></at>",
|
|
93
|
+
}
|
|
94
|
+
})
|
|
95
|
+
elif len(ats) > 0:
|
|
96
|
+
at_list_template = []
|
|
97
|
+
for at in ats:
|
|
98
|
+
at_list_template.append({
|
|
99
|
+
"tag": "div",
|
|
100
|
+
"text": {
|
|
101
|
+
"tag": "lark_md",
|
|
102
|
+
"content": "At <at id=" + at + "></at>",
|
|
103
|
+
}
|
|
104
|
+
})
|
|
105
|
+
data["card"]["elements"].extend(at_list_template)
|
|
106
|
+
else:
|
|
107
|
+
raise Exception("Invalid ats.")
|
|
108
|
+
|
|
109
|
+
if action:
|
|
110
|
+
if action["direction"] == "UP":
|
|
111
|
+
direction = "UP"
|
|
112
|
+
else:
|
|
113
|
+
direction = "DOWN"
|
|
114
|
+
|
|
115
|
+
if direction == "DOWN":
|
|
116
|
+
data["card"]["elements"].append({"tag": "hr"})
|
|
117
|
+
data["card"]["elements"].append(action["actions"])
|
|
118
|
+
if direction == "UP":
|
|
119
|
+
data["card"]["elements"].insert(0, action["actions"])
|
|
120
|
+
|
|
121
|
+
return self.post(data)
|
|
122
|
+
|
|
123
|
+
def post(self, data):
|
|
124
|
+
"""
|
|
125
|
+
发送消息(内容UTF-8编码)
|
|
126
|
+
:param data: 消息数据(字典)
|
|
127
|
+
:return: 返回消息发送结果
|
|
128
|
+
"""
|
|
129
|
+
try:
|
|
130
|
+
post_data = json.dumps(data)
|
|
131
|
+
response = requests.post(self.webhook, headers=self.headers, data=post_data, verify=False)
|
|
132
|
+
|
|
133
|
+
except requests.exceptions.HTTPError as exc:
|
|
134
|
+
logging.error("消息发送失败, HTTP error: %d, reason: %s" % (exc.response.status_code, exc.response.reason))
|
|
135
|
+
raise
|
|
136
|
+
except requests.exceptions.ConnectionError:
|
|
137
|
+
logging.error("消息发送失败,HTTP connection error!")
|
|
138
|
+
raise
|
|
139
|
+
except requests.exceptions.Timeout:
|
|
140
|
+
logging.error("消息发送失败,Timeout error!")
|
|
141
|
+
raise
|
|
142
|
+
except requests.exceptions.RequestException:
|
|
143
|
+
logging.error("消息发送失败, Request Exception!")
|
|
144
|
+
raise
|
|
145
|
+
else:
|
|
146
|
+
try:
|
|
147
|
+
result = response.json()
|
|
148
|
+
except JSONDecodeError:
|
|
149
|
+
logging.error("服务器响应异常,状态码:%s,响应内容:%s" % (response.status_code, response.text))
|
|
150
|
+
return {'errcode': 500, 'errmsg': '服务器响应异常'}
|
|
151
|
+
else:
|
|
152
|
+
logging.debug('发送结果:%s' % result)
|
|
153
|
+
# 消息发送失败提醒(errcode 不为 0,表示消息发送异常),默认不提醒,开发者可以根据返回的消息发送结果自行判断和处理
|
|
154
|
+
if self.fail_notice and result.get('errcode', True):
|
|
155
|
+
time_now = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time()))
|
|
156
|
+
error_data = {
|
|
157
|
+
"msgtype": "text",
|
|
158
|
+
"text": {
|
|
159
|
+
"content": "[注意-自动通知]飞书机器人消息发送失败,时间:%s,原因:%s,请及时跟进,谢谢!" % (
|
|
160
|
+
time_now, result['errmsg'] if result.get('errmsg', False) else '未知异常')
|
|
161
|
+
},
|
|
162
|
+
"at": {
|
|
163
|
+
"isAtAll": False
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
logging.error("消息发送失败,自动通知:%s" % error_data)
|
|
167
|
+
requests.post(self.webhook, headers=self.headers, data=json.dumps(error_data))
|
|
168
|
+
return result
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
if __name__ == '__main__':
|
|
172
|
+
lark_client = FeiShuTalkChatBot(webhook='https://open.feishu.cn/open-apis/bot/v2/hook/bba5b285-ca56-488c-9142-d8bcbcaee3c1')
|
|
173
|
+
#lark_client.success(message='testtesttesttesttesttest', title='测试标题')
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import hashlib
|
|
2
|
+
import uuid
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
"""
|
|
6
|
+
版本1 vs 版本2
|
|
7
|
+
|
|
8
|
+
特性 版本1(8位62进制) 版本2(22位62进制)
|
|
9
|
+
组合数 ~2.18e14(218万亿) ~3.2e35(远超UUID空间)
|
|
10
|
+
碰撞阈值 14.77百万(50%概率) 5.2e19(理论极高)
|
|
11
|
+
适用场景 小规模、临时ID 大规模、高唯一性需求
|
|
12
|
+
性能开销 低 中等(大整数转换)
|
|
13
|
+
扩展性 不可扩展 支持更高并发
|
|
14
|
+
|
|
15
|
+
版本2显著优于版本1:通过扩展ID长度至22位,将碰撞概率降至理论安全极限,适合金融、分布式系统等关键场景。
|
|
16
|
+
推荐实践:结合数据库唯一索引或Redis缓存,实时检测并重试碰撞ID,确保万无一失
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
printable_char = [
|
|
20
|
+
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
|
|
21
|
+
"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w",
|
|
22
|
+
"x", "y", "z",
|
|
23
|
+
"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W",
|
|
24
|
+
"X", "Y", "Z",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
"""版本2,生成最多 22 位的 ID
|
|
28
|
+
参考链接:# https://github.com/skorokithakis/shortuuid/tree/master
|
|
29
|
+
生成思路:根据 UUID3 或者 UUID4 得到的 32 位 16 进制数,转换成 62 进制的数
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def get_random_long_id(padding: Optional[int] = 22):
|
|
34
|
+
_tmp_id = get_random_uuid(with_dash=True)
|
|
35
|
+
return _convert_10_2_62(_tmp_id, padding)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def get_fix_long_id(base_str: str, padding: Optional[int] = 22):
|
|
39
|
+
_tmp_id = get_fix_uuid(long_id=base_str, with_dash=True)
|
|
40
|
+
return _convert_10_2_62(_tmp_id, padding)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _convert_10_2_62(id_16: str, padding: Optional[int]):
|
|
44
|
+
_tmp_id_10 = int(id_16, 16)
|
|
45
|
+
|
|
46
|
+
output = ""
|
|
47
|
+
while _tmp_id_10:
|
|
48
|
+
_tmp_id_10, digit = divmod(_tmp_id_10, 62)
|
|
49
|
+
output += printable_char[digit]
|
|
50
|
+
if padding:
|
|
51
|
+
remainder = max(padding - len(output), 0)
|
|
52
|
+
output += printable_char[0] * remainder
|
|
53
|
+
|
|
54
|
+
return output[::-1]
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
"""版本1,生成 8 位数的短 ID
|
|
58
|
+
参考链接:https://www.cnblogs.com/shouke/archive/2020/08/02/13423073.html
|
|
59
|
+
生成思路:根据 UUID3 或者 UUID4 得到的 32 位 16 进制数进行分组,每组 4 个,一共 8 组。对每一组的数字对 62 取余。
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def get_random_short_id():
|
|
64
|
+
_tmp_id = get_random_uuid(with_dash=True)
|
|
65
|
+
_buffer = []
|
|
66
|
+
# 循环 8 次,每次取出 4 个
|
|
67
|
+
for i in range(0, 8):
|
|
68
|
+
start = i * 4
|
|
69
|
+
end = i * 4 + 4
|
|
70
|
+
# 将 16 进制的 uuid 转换成 10 进制的数字
|
|
71
|
+
val = int(_tmp_id[start:end], 16)
|
|
72
|
+
_buffer.append(printable_char[val % 62])
|
|
73
|
+
return "".join(_buffer)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def get_fix_short_id(long_id):
|
|
77
|
+
_tmp_id = get_fix_uuid(long_id=long_id, with_dash=True)
|
|
78
|
+
_buffer = []
|
|
79
|
+
for i in range(0, 8):
|
|
80
|
+
start = i * 4
|
|
81
|
+
end = i * 4 + 4
|
|
82
|
+
val = int(_tmp_id[start:end], 16)
|
|
83
|
+
_buffer.append(printable_char[val % 62])
|
|
84
|
+
short_id = "".join(_buffer)
|
|
85
|
+
return short_id
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
"""使用 UUID3 和 UUID4 分别得到固定的和随机的 UUID
|
|
89
|
+
Python 的 UUID3 生成的 ID 是 128 位的十进制数,可以表示为 32 位的十六进制数位。
|
|
90
|
+
Python 的 UUID3 生成的 ID 在给定相同的命名空间和名称的情况下是唯一的,它是通过将命名空间和名称作为输入,应用 MD5 哈希算法生成的。
|
|
91
|
+
由于 MD5 算法的特性,即使输入的命名空间和名称有微小的变化,生成的 ID 也会有很大的差异,从而保证了唯一性。
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def get_random_uuid(with_dash=False):
|
|
96
|
+
if with_dash:
|
|
97
|
+
return str(uuid.uuid4()).replace("-", '')
|
|
98
|
+
else:
|
|
99
|
+
return str(uuid.uuid4())
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def get_fix_uuid(long_id, with_dash=False):
|
|
103
|
+
if with_dash:
|
|
104
|
+
return str(uuid.uuid3(uuid.NAMESPACE_DNS, long_id)).replace("-", '')
|
|
105
|
+
else:
|
|
106
|
+
return str(uuid.uuid3(uuid.NAMESPACE_DNS, long_id))
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def get_file_md5(filepath):
|
|
110
|
+
with open(filepath, 'rb+') as f:
|
|
111
|
+
md5obj = hashlib.md5()
|
|
112
|
+
md5obj.update(f.read())
|
|
113
|
+
hash_ = md5obj.hexdigest()
|
|
114
|
+
return str(hash_).upper()
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
import json
|
|
3
|
+
from enum import Enum
|
|
4
|
+
from typing import List, Optional
|
|
5
|
+
import pytz
|
|
6
|
+
from deprecated.sphinx import deprecated
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class DateTimeFormat(Enum):
|
|
10
|
+
'''Orbit date format
|
|
11
|
+
'''
|
|
12
|
+
|
|
13
|
+
DATETIME_FORMAT = "%Y-%m-%dT%H:%M:%S%z"
|
|
14
|
+
DATE_FORMAT = "%Y-%m-%d"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class DateMaxMin(Enum):
|
|
18
|
+
'''Orbit date time min & max range
|
|
19
|
+
'''
|
|
20
|
+
|
|
21
|
+
DATE_MIN = "1000-01-01"
|
|
22
|
+
DATE_MAX = "2100-12-31"
|
|
23
|
+
|
|
24
|
+
DATETIME_MIN_UTC = "1000-01-01T00:00:00+0000"
|
|
25
|
+
DATETIME_MAX_UTC = "2100-12-31T29:59:59+0000"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class DateEncoder(json.JSONEncoder):
|
|
29
|
+
def default(self, obj):
|
|
30
|
+
if isinstance(obj, datetime.datetime):
|
|
31
|
+
return obj.strftime(DateTimeFormat.DATETIME_FORMAT.value)
|
|
32
|
+
elif isinstance(obj, datetime.date):
|
|
33
|
+
return obj.strftime(DateTimeFormat.DATE_FORMAT.value)
|
|
34
|
+
else:
|
|
35
|
+
return json.JSONEncoder.default(self, obj)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# -------------------------------------------------------------------------------------------------------------
|
|
39
|
+
# Below 2 methods are for getting Orbit standard datetime.
|
|
40
|
+
# -------------------------------------------------------------------------------------------------------------
|
|
41
|
+
@deprecated(version="v1", reason="Use get_orbit_std_datetime instead.")
|
|
42
|
+
def get_orbit_std_datatime():
|
|
43
|
+
current_timezone = datetime.datetime.now(datetime.timezone.utc).astimezone().tzinfo
|
|
44
|
+
return datetime.datetime.now(tz=current_timezone).strftime(DateTimeFormat.DATETIME_FORMAT.value)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@deprecated(version="v1", reason="Use get_orbit_std_datetime_utc instead.")
|
|
48
|
+
def get_orbit_std_datatime_utc():
|
|
49
|
+
return datetime.datetime.now(tz=pytz.timezone('UTC')).strftime(DateTimeFormat.DATETIME_FORMAT.value)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def get_orbit_std_datetime():
|
|
53
|
+
current_timezone = datetime.datetime.now(datetime.timezone.utc).astimezone().tzinfo
|
|
54
|
+
return datetime.datetime.now(tz=current_timezone).strftime(DateTimeFormat.DATETIME_FORMAT.value)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def get_orbit_std_datetime_utc():
|
|
58
|
+
return datetime.datetime.now(tz=pytz.timezone('UTC')).strftime(DateTimeFormat.DATETIME_FORMAT.value)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
# -------------------------------------------------------------------------------------------------------------
|
|
62
|
+
# Below 2 methods are for getting Orbit standard date.
|
|
63
|
+
# -------------------------------------------------------------------------------------------------------------
|
|
64
|
+
def get_orbit_std_date():
|
|
65
|
+
return datetime.datetime.now(
|
|
66
|
+
tz=datetime.datetime.now(datetime.timezone.utc).astimezone().tzinfo
|
|
67
|
+
).strftime(DateTimeFormat.DATE_FORMAT.value)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def get_orbit_std_date_utc():
|
|
71
|
+
return datetime.datetime.now(tz=pytz.timezone('UTC')).strftime(DateTimeFormat.DATE_FORMAT.value)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def get_date_range_by_base(date_range: int,
|
|
75
|
+
base_date: Optional[datetime.datetime] = None,
|
|
76
|
+
direction: str = "backward",
|
|
77
|
+
formatter: str = "%Y-%m-%d") -> List:
|
|
78
|
+
"""
|
|
79
|
+
Get date range by datetime.
|
|
80
|
+
:param date_range:
|
|
81
|
+
:param base_date:
|
|
82
|
+
:param direction:
|
|
83
|
+
:param formatter:
|
|
84
|
+
:return: A list of date range.
|
|
85
|
+
"""
|
|
86
|
+
if date_range <= 0:
|
|
87
|
+
raise Exception("Param err")
|
|
88
|
+
|
|
89
|
+
if base_date is None:
|
|
90
|
+
base_date = datetime.datetime.now()
|
|
91
|
+
|
|
92
|
+
timedelta = datetime.timedelta(days=date_range - 1)
|
|
93
|
+
if direction == "backward":
|
|
94
|
+
start_date = base_date - timedelta
|
|
95
|
+
end_date = base_date
|
|
96
|
+
elif direction == "forward":
|
|
97
|
+
start_date = base_date
|
|
98
|
+
end_date = base_date + timedelta
|
|
99
|
+
else:
|
|
100
|
+
raise Exception("Param err")
|
|
101
|
+
|
|
102
|
+
return get_date_range_list_v2(start_date.strftime("%Y-%m-%d"), end_date.strftime("%Y-%m-%d"), post_formatter=formatter)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def get_date_range_list_v1(start_date, end_date) -> List:
|
|
106
|
+
"""
|
|
107
|
+
This method will return a list in which all date range are included with the very start and ending.
|
|
108
|
+
:param start_date: '2019-06-03'
|
|
109
|
+
:param end_date: '2019-06-05'
|
|
110
|
+
:return:
|
|
111
|
+
"""
|
|
112
|
+
datestart = datetime.datetime.strptime(start_date, "%Y-%m-%d")
|
|
113
|
+
dateend = datetime.datetime.strptime(end_date, "%Y-%m-%d")
|
|
114
|
+
dateend += datetime.timedelta(days=1)
|
|
115
|
+
|
|
116
|
+
day_list_with_range = []
|
|
117
|
+
while datestart < dateend:
|
|
118
|
+
day_list_with_range.append(datestart.strftime("%Y-%m-%d"))
|
|
119
|
+
datestart += datetime.timedelta(days=1)
|
|
120
|
+
|
|
121
|
+
return day_list_with_range
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def get_date_range_list_v2(start_date, end_date, pre_formatter="%Y-%m-%d", post_formatter="%Y-%m-%d") -> List:
|
|
125
|
+
"""
|
|
126
|
+
This method will return a list in which all date range are included with the very start and ending.
|
|
127
|
+
:param post_formatter: Default is %Y-%m-%d
|
|
128
|
+
:param pre_formatter: Default is %Y-%m-%d
|
|
129
|
+
:param start_date: '2019-06-03'
|
|
130
|
+
:param end_date: '2019-06-05'
|
|
131
|
+
:return:
|
|
132
|
+
"""
|
|
133
|
+
datestart = datetime.datetime.strptime(start_date, pre_formatter)
|
|
134
|
+
dateend = datetime.datetime.strptime(end_date, pre_formatter)
|
|
135
|
+
dateend += datetime.timedelta(days=1)
|
|
136
|
+
|
|
137
|
+
day_list_with_range = []
|
|
138
|
+
while datestart < dateend:
|
|
139
|
+
day_list_with_range.append(datestart.strftime(post_formatter))
|
|
140
|
+
datestart += datetime.timedelta(days=1)
|
|
141
|
+
|
|
142
|
+
return day_list_with_range
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def get_next_day(day: str, interval: int = 1, date_format="%Y-%m-%d") -> str:
|
|
146
|
+
"""
|
|
147
|
+
Get the next day by given date
|
|
148
|
+
:param day:
|
|
149
|
+
:param interval:
|
|
150
|
+
:param date_format:
|
|
151
|
+
:return:
|
|
152
|
+
"""
|
|
153
|
+
|
|
154
|
+
target_day = datetime.datetime.strptime(day, date_format)
|
|
155
|
+
next_day = target_day + datetime.timedelta(days=interval)
|
|
156
|
+
return next_day.strftime(date_format)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def get_next_workday_cn(workday: str, interval: int = 1, date_format: str = "%Y-%m-%d") -> str:
|
|
160
|
+
"""
|
|
161
|
+
Get the next trading date by Chinese calendar
|
|
162
|
+
:param workday:
|
|
163
|
+
:param interval:
|
|
164
|
+
:param date_format:
|
|
165
|
+
:return:
|
|
166
|
+
"""
|
|
167
|
+
|
|
168
|
+
try:
|
|
169
|
+
from chinese_calendar import is_workday
|
|
170
|
+
except ImportError:
|
|
171
|
+
raise ValueError(
|
|
172
|
+
"Please install below packages before using this function.\n"
|
|
173
|
+
"- chinese_calendar"
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
# Start to judge
|
|
177
|
+
next_date = datetime.datetime.strptime(
|
|
178
|
+
get_next_day(day=workday, interval=interval, date_format=date_format),
|
|
179
|
+
date_format
|
|
180
|
+
)
|
|
181
|
+
while not is_workday(next_date) or next_date.weekday() in (5, 6):
|
|
182
|
+
next_date = datetime.datetime.strptime(
|
|
183
|
+
get_next_day(day=next_date.strftime(date_format), interval=interval, date_format=date_format),
|
|
184
|
+
date_format
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
return next_date.strftime(date_format)
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import re
|
|
3
|
+
|
|
4
|
+
import oss2
|
|
5
|
+
from oss2.credentials import EnvironmentVariableCredentialsProvider
|
|
6
|
+
from .util_type_mapping import get_content_type_4_filename
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
os.environ['OSS_ACCESS_KEY_ID'] = 'LTAI5tLtyiy18UAWL626k1wC'
|
|
10
|
+
os.environ['OSS_ACCESS_KEY_SECRET'] = 'T6VSV1QFn7hsc8wjrOGG8K4meDyXbX'
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class OssCloudObjectProvider:
|
|
14
|
+
|
|
15
|
+
def __init__(self):
|
|
16
|
+
self.oss_endpoint = 'https://oss-cn-shanghai.aliyuncs.com'
|
|
17
|
+
self.region = 'cn-shanghai'
|
|
18
|
+
self.auth = oss2.ProviderAuthV4(EnvironmentVariableCredentialsProvider())
|
|
19
|
+
|
|
20
|
+
def oss_split_path(self, oss_path):
|
|
21
|
+
if not oss_path.startswith('oss://'):
|
|
22
|
+
raise Exception(f"Invalid oss path format: {oss_path}")
|
|
23
|
+
oss_path_re = re.compile(r"(oss://[a-zA-Z\-_0-9]+)/(.+)")
|
|
24
|
+
path_group = oss_path_re.search(oss_path).groups()
|
|
25
|
+
bucket = path_group[0].replace('oss://', '')
|
|
26
|
+
if not bucket:
|
|
27
|
+
raise Exception(f"Invalid oss path format: {oss_path}")
|
|
28
|
+
key = path_group[1]
|
|
29
|
+
if not key:
|
|
30
|
+
raise Exception(f"Invalid oss path format: {oss_path}")
|
|
31
|
+
return bucket, key
|
|
32
|
+
|
|
33
|
+
def copy_oss_obj(self, src_oss_path: str, dst_oss_path: str):
|
|
34
|
+
src_bucket, src_key = self.oss_split_path(src_oss_path)
|
|
35
|
+
dst_bucket, dst_key = self.oss_split_path(dst_oss_path)
|
|
36
|
+
bucket_obj = oss2.Bucket(self.auth, self.oss_endpoint, dst_bucket, region=self.region)
|
|
37
|
+
result = bucket_obj.copy_object(src_bucket, src_key, dst_key)
|
|
38
|
+
if result.status != 200:
|
|
39
|
+
raise Exception(f"Copy failed with status code {result.status}")
|
|
40
|
+
|
|
41
|
+
def check_file_exist(self, oss_path: str) -> bool:
|
|
42
|
+
bucket, key = self.oss_split_path(oss_path)
|
|
43
|
+
bucket_obj = oss2.Bucket(self.auth, self.oss_endpoint, bucket, region=self.region)
|
|
44
|
+
oss_result = bucket_obj.object_exists(key)
|
|
45
|
+
return oss_result
|
|
46
|
+
|
|
47
|
+
def upload_file(self, oss_path: str, local_path: str):
|
|
48
|
+
if not os.path.exists(local_path):
|
|
49
|
+
raise Exception("Local file doesn't exist!")
|
|
50
|
+
bucket, key = self.oss_split_path(oss_path)
|
|
51
|
+
bucket_obj = oss2.Bucket(self.auth, self.oss_endpoint, bucket, region=self.region)
|
|
52
|
+
content_type = get_content_type_4_filename(local_path, text_with_utf8=True)
|
|
53
|
+
result = bucket_obj.put_object_from_file(key, local_path, headers={"Content-Type": content_type})
|
|
54
|
+
if result.status != 200:
|
|
55
|
+
raise Exception(f"Upload failed with status code {result.status}")
|
|
56
|
+
|
|
57
|
+
def download_file(self, oss_path: str, local_path: str, filename: str):
|
|
58
|
+
if not os.path.exists(local_path):
|
|
59
|
+
os.makedirs(local_path)
|
|
60
|
+
bucket, key = self.oss_split_path(oss_path)
|
|
61
|
+
bucket_obj = oss2.Bucket(self.auth, self.oss_endpoint, bucket, region=self.region)
|
|
62
|
+
bucket_obj.get_object_to_file(key, os.path.join(local_path, filename))
|
|
63
|
+
|
|
64
|
+
def get_presign_url_docs(self, oss_path: str, expire_hours: int = 1) -> str:
|
|
65
|
+
bucket, key = self.oss_split_path(oss_path)
|
|
66
|
+
bucket_obj = oss2.Bucket(self.auth, 'http://docs.huanai.tech', bucket, region=self.region, is_cname=True)
|
|
67
|
+
expire_seconds = expire_hours * 60 * 60
|
|
68
|
+
return bucket_obj.sign_url('GET', key, expire_seconds, slash_safe=True)
|
|
69
|
+
|
|
70
|
+
def get_upload_url(self, oss_path: str, expire_hours: int = 1) -> str:
|
|
71
|
+
bucket, key = self.oss_split_path(oss_path)
|
|
72
|
+
bucket_obj = oss2.Bucket(self.auth, self.oss_endpoint, bucket, region=self.region)
|
|
73
|
+
expire_seconds = expire_hours * 60 * 60
|
|
74
|
+
return bucket_obj.sign_url('PUT', key, expire_seconds, slash_safe=True)
|
|
75
|
+
|
|
76
|
+
def list_dir_iter(self, oss_path_pre: str):
|
|
77
|
+
bucket, key_pre = self.oss_split_path(oss_path_pre)
|
|
78
|
+
bucket = oss2.Bucket(self.auth, self.oss_endpoint, bucket, region=self.region)
|
|
79
|
+
for obj in oss2.ObjectIterator(bucket, prefix=key_pre):
|
|
80
|
+
yield obj.key
|
|
81
|
+
|
|
82
|
+
def get_file_content(self, oss_path: str):
|
|
83
|
+
bucket, key = self.oss_split_path(oss_path)
|
|
84
|
+
bucket_obj = oss2.Bucket(self.auth, self.oss_endpoint, bucket, region=self.region)
|
|
85
|
+
return bucket_obj.get_object(key).read()
|
|
86
|
+
|
|
87
|
+
def delete_file_from_oss(self, oss_path: str):
|
|
88
|
+
bucket, key = self.oss_split_path(oss_path)
|
|
89
|
+
bucket_obj = oss2.Bucket(self.auth, self.oss_endpoint, bucket, region=self.region)
|
|
90
|
+
result = bucket_obj.delete_object(key)
|
|
91
|
+
if result.status != 204:
|
|
92
|
+
raise Exception(f"Delete failed with status code {result.status}")
|
|
93
|
+
|
|
94
|
+
# if __name__ == '__main__':
|
|
95
|
+
# oss_client = OssCloudObjectProvider(OSSAPPID, OSSPWD, OSSENDPOINT)
|
|
96
|
+
# # oss_client.upload_file('oss://dev-test-ha/tabel1.pdf', 'table1.pdf')
|
|
97
|
+
# url = oss_client.get_presign_url(f'oss://dev-test-ha/tabel1.pdf')
|
|
98
|
+
# print(url)
|
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
import threading
|
|
2
|
+
from enum import Enum
|
|
3
|
+
|
|
4
|
+
'''
|
|
5
|
+
Reference link: https://www.lifewire.com/file-extensions-and-mime-types-3469109
|
|
6
|
+
Reference link: https://superuser.com/questions/62852/xlsx-files-are-being-interpreted-as-zip-files-on-download-why
|
|
7
|
+
# Applications
|
|
8
|
+
doc / docx / ppt / pptx / xls / xlsx / pdf / json / js
|
|
9
|
+
# Text Files
|
|
10
|
+
html / htm / txt
|
|
11
|
+
'''
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ExtenCons(Enum):
|
|
15
|
+
'''
|
|
16
|
+
Useful constant variables
|
|
17
|
+
'''
|
|
18
|
+
|
|
19
|
+
EXTEN_OCTET_STREAM = 'application/octet-stream'
|
|
20
|
+
# Application
|
|
21
|
+
EXTEN_APPLI_PDF = 'application/pdf'
|
|
22
|
+
EXTEN_APPLI_XML = 'application/xml'
|
|
23
|
+
EXTEN_APPLI_JSON = 'application/json'
|
|
24
|
+
EXTEN_APPLI_JS = 'application/x-javascript'
|
|
25
|
+
EXTEN_APPLI_DOC = 'application/msword'
|
|
26
|
+
EXTEN_APPLI_DOCX = 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
|
|
27
|
+
EXTEN_APPLI_XLS = 'application/vnd.ms-excel'
|
|
28
|
+
EXTEN_APPLI_XLSX = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
|
29
|
+
EXTEN_APPLI_PPT = 'application/vnd.ms-powerpoint'
|
|
30
|
+
EXTEN_APPLI_PPTX = 'application/vnd.openxmlformats-officedocument.presentationml.presentation'
|
|
31
|
+
# Text Files
|
|
32
|
+
EXTEN_TEXT_HTML = 'text/html'
|
|
33
|
+
EXTEN_TEXT_HTM = 'text/html'
|
|
34
|
+
EXTEN_TEXT_TXT = 'text/plain'
|
|
35
|
+
# Text Files with charset
|
|
36
|
+
EXTEN_TEXT_HTML_UTF8 = 'text/html;charset=utf-8'
|
|
37
|
+
EXTEN_TEXT_HTM_UTF8 = 'text/html;charset=utf-8'
|
|
38
|
+
EXTEN_TEXT_TXT_UTF8 = 'text/plain;charset=utf-8'
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
_content_file_mapping = {
|
|
42
|
+
# MIME Types: Applications
|
|
43
|
+
'*': {'application': 'Binary file', 'mine': 'application/octet-stream'},
|
|
44
|
+
'pdf': {'application': 'pdf document', 'mine': 'application/pdf'},
|
|
45
|
+
'json': {'application': 'json document', 'mine': 'application/json'},
|
|
46
|
+
'evy': {'application': 'Corel Envoy', 'mine': 'application/envoy'},
|
|
47
|
+
'xml': {'application': 'XML document', 'mine': 'application/xml'},
|
|
48
|
+
# Word related
|
|
49
|
+
'dot': {'application': 'Word document template', 'mine': 'application/msword'},
|
|
50
|
+
'doc': {'application': 'Word document', 'mine': 'application/msword'},
|
|
51
|
+
'docx': {'application': 'docx document', 'mine': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'},
|
|
52
|
+
'docm': {'application': '', 'mine': 'application/vnd.ms-word.document.macroEnabled.12'}, # Added in 07 Mar, 2024
|
|
53
|
+
'dotx': {'application': '', 'mine': 'application/vnd.openxmlformats-officedocument.wordprocessingml.template'}, # Added in 07 Mar, 2024
|
|
54
|
+
'fif': {'application': 'fractal image file', 'mine': 'application/fractals'},
|
|
55
|
+
'spl': {'application': 'Windows print spool file', 'mine': 'application/futuresplash'},
|
|
56
|
+
'hta': {'application': 'HTML application', 'mine': 'application/hta'},
|
|
57
|
+
'acx': {'application': 'Atari ST Program', 'mine': 'application/internet-property-stream'},
|
|
58
|
+
'hqx': {'application': 'BinHex encoded file', 'mine': 'application/mac-binhex40'},
|
|
59
|
+
'bin': {'application': 'binary disk image', 'mine': 'application/octet-stream'},
|
|
60
|
+
'class': {'application': 'Java class file', 'mine': 'application/octet-stream'},
|
|
61
|
+
'dms': {'application': 'Disk Masher image', 'mine': 'application/octet-stream'},
|
|
62
|
+
'exe': {'application': 'executable file', 'mine': 'application/octet-stream'},
|
|
63
|
+
'lha': {'application': 'LHARC compressed archive', 'mine': 'application/octet-stream'},
|
|
64
|
+
'lzh': {'application': 'LZH compressed file', 'mine': 'application/octet-stream'},
|
|
65
|
+
'oda': {'application': 'CALS raster image', 'mine': 'application/oda'},
|
|
66
|
+
'axs': {'application': 'ActiveX script', 'mine': 'application/olescript'},
|
|
67
|
+
'prf': {'application': 'Outlook profile file', 'mine': 'application/pics-rules'},
|
|
68
|
+
'p10': {'application': 'certificate request file', 'mine': 'application/pkcs10'},
|
|
69
|
+
'crl': {'application': 'certificate revocation list file', 'mine': 'application/pkix-crl'},
|
|
70
|
+
'ai': {'application': 'Adobe Illustrator file', 'mine': 'application/postscript'},
|
|
71
|
+
'eps': {'application': 'postscript file', 'mine': 'application/postscript'},
|
|
72
|
+
'ps': {'application': 'postscript file', 'mine': 'application/postscript'},
|
|
73
|
+
'rtf': {'application': 'rich text format file', 'mine': 'application/rtf'},
|
|
74
|
+
'setpay': {'application': 'set payment initiation', 'mine': 'application/set-payment-initiation'},
|
|
75
|
+
'setreg': {'application': 'set registration initiation', 'mine': 'application/set-registration-initiation'},
|
|
76
|
+
# Excel related
|
|
77
|
+
'xla': {'application': 'Excel Add-in file', 'mine': 'application/vnd.ms-excel'},
|
|
78
|
+
'xlc': {'application': 'Excel chart', 'mine': 'application/vnd.ms-excel'},
|
|
79
|
+
'xlm': {'application': 'Excel macro', 'mine': 'application/vnd.ms-excel'},
|
|
80
|
+
'xls': {'application': 'Excel spreadsheet', 'mine': 'application/vnd.ms-excel'},
|
|
81
|
+
'xlsx': {'application': 'xlsx document', 'mine': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'},
|
|
82
|
+
'xlt': {'application': 'Excel template', 'mine': 'application/vnd.ms-excel'},
|
|
83
|
+
'xlw': {'application': 'Excel worspace', 'mine': 'application/vnd.ms-excel'},
|
|
84
|
+
'xlam': {'application': '', 'mine': 'application/vnd.ms-excel.addin.macroEnabled.12'}, # Added in 07 Mar, 2024
|
|
85
|
+
'xlsb': {'application': '', 'mine': 'application/vnd.ms-excel.sheet.binary.macroEnabled.12'}, # Added in 07 Mar, 2024
|
|
86
|
+
'xlsm': {'application': '', 'mine': 'application/vnd.ms-excel.sheet.macroEnabled.12'}, # Added in 07 Mar, 2024
|
|
87
|
+
'xltm': {'application': '', 'mine': 'application/vnd.ms-excel.template.macroEnabled.12'}, # Added in 07 Mar, 2024
|
|
88
|
+
'xltx': {'application': '', 'mine': 'application/vnd.openxmlformats-officedocument.spreadsheetml.template'}, # Added in 07 Mar, 2024
|
|
89
|
+
'msg': {'application': 'Outlook mail message', 'mine': 'application/vnd.ms-outlook'},
|
|
90
|
+
'sst': {'application': 'serialized certificate store file', 'mine': 'application/vnd.ms-pkicertstore'},
|
|
91
|
+
'cat': {'application': 'Windows catalog file', 'mine': 'application/vnd.ms-pkiseccat'},
|
|
92
|
+
'stl': {'application': 'stereolithography file', 'mine': 'application/vnd.ms-pkistl'},
|
|
93
|
+
# ppt related
|
|
94
|
+
'pot': {'application': 'PowerPoint template', 'mine': 'application/vnd.ms-powerpoint'},
|
|
95
|
+
'pps': {'application': 'PowerPoint slide show', 'mine': 'application/vnd.ms-powerpoint'},
|
|
96
|
+
'ppt': {'application': 'PowerPoint presentation', 'mine': 'application/vnd.ms-powerpoint'},
|
|
97
|
+
'pptx': {'application': 'pptx document', 'mine': 'application/vnd.openxmlformats-officedocument.presentationml.presentation'},
|
|
98
|
+
'potm': {'application': '', 'mine': 'application/vnd.ms-powerpoint.template.macroEnabled.12'}, # Added in 07 Mar, 2024
|
|
99
|
+
'potx': {'application': '', 'mine': 'application/vnd.openxmlformats-officedocument.presentationml.template'}, # Added in 07 Mar, 2024
|
|
100
|
+
'ppam': {'application': '', 'mine': 'application/vnd.ms-powerpoint.addin.macroEnabled.12'}, # Added in 07 Mar, 2024
|
|
101
|
+
'ppsm': {'application': '', 'mine': 'application/vnd.ms-powerpoint.slideshow.macroEnabled.12'}, # Added in 07 Mar, 2024
|
|
102
|
+
'ppsx': {'application': '', 'mine': 'application/vnd.openxmlformats-officedocument.presentationml.slideshow'}, # Added in 07 Mar, 2024
|
|
103
|
+
'pptm': {'application': '', 'mine': 'application/vnd.ms-powerpoint.presentation.macroEnabled.12'}, # Added in 07 Mar, 2024
|
|
104
|
+
'mpp': {'application': 'Microsoft Project file', 'mine': 'application/vnd.ms-project'},
|
|
105
|
+
'wcm': {'application': 'WordPerfect macro', 'mine': 'application/vnd.ms-works'},
|
|
106
|
+
'wdb': {'application': 'Microsoft Works database', 'mine': 'application/vnd.ms-works'},
|
|
107
|
+
'wks': {'application': 'Microsoft Works spreadsheet', 'mine': 'application/vnd.ms-works'},
|
|
108
|
+
'wps': {'application': 'Microsoft Works word processsor document', 'mine': 'application/vnd.ms-works'},
|
|
109
|
+
'hlp': {'application': 'Windows help file', 'mine': 'application/winhlp'},
|
|
110
|
+
'bcpio': {'application': 'binary CPIO archive', 'mine': 'application/x-bcpio'},
|
|
111
|
+
'cdf': {'application': 'computable document format file', 'mine': 'application/x-cdf'},
|
|
112
|
+
'z': {'application': 'Unix compressed file', 'mine': 'application/x-compress'},
|
|
113
|
+
'tgz': {'application': 'gzipped tar file', 'mine': 'application/x-compressed'},
|
|
114
|
+
'cpio': {'application': 'Unix CPIO archive', 'mine': 'application/x-cpio'},
|
|
115
|
+
'csh': {'application': 'Photoshop custom shapes file', 'mine': 'application/x-csh'},
|
|
116
|
+
'dcr': {'application': 'Kodak RAW image file', 'mine': 'application/x-director'},
|
|
117
|
+
'dir': {'application': 'Adobe Director movie', 'mine': 'application/x-director'},
|
|
118
|
+
'dxr': {'application': 'Macromedia Director movie', 'mine': 'application/x-director'},
|
|
119
|
+
'dvi': {'application': 'device independent format file', 'mine': 'application/x-dvi'},
|
|
120
|
+
'gtar': {'application': 'Gnu tar archive', 'mine': 'application/x-gtar'},
|
|
121
|
+
'gz': {'application': 'Gnu zipped archive', 'mine': 'application/x-gzip'},
|
|
122
|
+
'hdf': {'application': 'hierarchical data format file', 'mine': 'application/x-hdf'},
|
|
123
|
+
'ins': {'application': 'internet settings file', 'mine': 'application/x-internet-signup'},
|
|
124
|
+
'isp': {'application': 'IIS internet service provider settings', 'mine': 'application/x-internet-signup'},
|
|
125
|
+
'iii': {'application': 'ARC+ architectural file', 'mine': 'application/x-iphone'},
|
|
126
|
+
'js': {'application': 'JavaScript file', 'mine': 'application/x-javascript'},
|
|
127
|
+
'latex': {'application': 'LaTex document', 'mine': 'application/x-latex'},
|
|
128
|
+
'mdb': {'application': 'Microsoft Access database', 'mine': 'application/x-msaccess'},
|
|
129
|
+
'crd': {'application': 'Windows CardSpace file', 'mine': 'application/x-mscardfile'},
|
|
130
|
+
'clp': {'application': 'CrazyTalk clip file', 'mine': 'application/x-msclip'},
|
|
131
|
+
'dll': {'application': 'dynamic link library', 'mine': 'application/x-msdownload'},
|
|
132
|
+
'm13': {'application': 'Microsoft media viewer file', 'mine': 'application/x-msmediaview'},
|
|
133
|
+
'm14': {'application': 'Steuer2001 file', 'mine': 'application/x-msmediaview'},
|
|
134
|
+
'mvb': {'application': 'multimedia viewer book source file', 'mine': 'application/x-msmediaview'},
|
|
135
|
+
'wmf': {'application': 'Windows meta file', 'mine': 'application/x-msmetafile'},
|
|
136
|
+
'mny': {'application': 'Microsoft Money file', 'mine': 'application/x-msmoney'},
|
|
137
|
+
'pub': {'application': 'Microsoft Publisher file', 'mine': 'application/x-mspublisher'},
|
|
138
|
+
'scd': {'application': 'Turbo Tax tax schedule list', 'mine': 'application/x-msschedule'},
|
|
139
|
+
'trm': {'application': 'FTR media file', 'mine': 'application/x-msterminal'},
|
|
140
|
+
'wri': {'application': 'Microsoft Write file', 'mine': 'application/x-mswrite'},
|
|
141
|
+
'nc': {'application': 'Mastercam numerical control file', 'mine': 'application/x-netcdf'},
|
|
142
|
+
'pma': {'application': 'MSX computers archive format', 'mine': 'application/x-perfmon'},
|
|
143
|
+
'pmc': {'application': 'performance monitor counter file', 'mine': 'application/x-perfmon'},
|
|
144
|
+
'pml': {'application': 'process monitor log file', 'mine': 'application/x-perfmon'},
|
|
145
|
+
'pmr': {'application': 'Avid persistant media record file', 'mine': 'application/x-perfmon'},
|
|
146
|
+
'pmw': {'application': 'Pegasus Mail draft stored message', 'mine': 'application/x-perfmon'},
|
|
147
|
+
'p12': {'application': 'personal information exchange file', 'mine': 'application/x-pkcs12'},
|
|
148
|
+
'pfx': {'application': 'PKCS #12 certificate file', 'mine': 'application/x-pkcs12'},
|
|
149
|
+
'p7b': {'application': 'PKCS #7 certificate file', 'mine': 'application/x-pkcs7-certificates'},
|
|
150
|
+
'spc': {'application': 'software publisher certificate file', 'mine': 'application/x-pkcs7-certificates'},
|
|
151
|
+
'p7r': {'application': 'certificate request response file', 'mine': 'application/x-pkcs7-certreqresp'},
|
|
152
|
+
'p7c': {'application': 'PKCS #7 certificate file', 'mine': 'application/x-pkcs7-mime'},
|
|
153
|
+
'p7m': {'application': 'digitally encrypted message', 'mine': 'application/x-pkcs7-mime'},
|
|
154
|
+
'p7s': {'application': 'digitally signed email message', 'mine': 'application/x-pkcs7-signature'},
|
|
155
|
+
'sh': {'application': 'Bash shell script', 'mine': 'application/x-sh'},
|
|
156
|
+
'shar': {'application': 'Unix shar archive', 'mine': 'application/x-shar'},
|
|
157
|
+
'swf': {'application': 'Flash file', 'mine': 'application/x-shockwave-flash'},
|
|
158
|
+
'sit': {'application': 'Stuffit archive file', 'mine': 'application/x-stuffit'},
|
|
159
|
+
'sv4cpio': {'application': 'system 5 release 4 CPIO file', 'mine': 'application/x-sv4cpio'},
|
|
160
|
+
'sv4crc': {'application': 'system 5 release 4 CPIO checksum data', 'mine': 'application/x-sv4crc'},
|
|
161
|
+
'tar': {'application': 'consolidated Unix file archive', 'mine': 'application/x-tar'},
|
|
162
|
+
'tcl': {'application': 'Tcl script', 'mine': 'application/x-tcl'},
|
|
163
|
+
'tex': {'application': 'LaTeX source document', 'mine': 'application/x-tex'},
|
|
164
|
+
'texi': {'application': 'LaTeX info document', 'mine': 'application/x-texinfo'},
|
|
165
|
+
'texinfo': {'application': 'LaTeX info document', 'mine': 'application/x-texinfo'},
|
|
166
|
+
'roff': {'application': 'unformatted manual page', 'mine': 'application/x-troff'},
|
|
167
|
+
't': {'application': 'Turing source code file', 'mine': 'application/x-troff'},
|
|
168
|
+
'tr': {'application': 'TomeRaider 2 ebook file', 'mine': 'application/x-troff'},
|
|
169
|
+
'man': {'application': 'Unix manual', 'mine': 'application/x-troff-man'},
|
|
170
|
+
'me': {'application': 'readme text file', 'mine': 'application/x-troff-me'},
|
|
171
|
+
'ms': {'application': '3ds Max script file', 'mine': 'application/x-troff-ms'},
|
|
172
|
+
'ustar': {'application': 'uniform standard tape archive format file', 'mine': 'application/x-ustar'},
|
|
173
|
+
'src': {'application': 'source code', 'mine': 'application/x-wais-source'},
|
|
174
|
+
'cer': {'application': 'internet security certificate', 'mine': 'application/x-x509-ca-cert'},
|
|
175
|
+
'crt': {'application': 'security certificate', 'mine': 'application/x-x509-ca-cert'},
|
|
176
|
+
'der': {'application': 'DER certificate file', 'mine': 'application/x-x509-ca-cert'},
|
|
177
|
+
'pko': {'application': 'public key security object', 'mine': 'application/ynd.ms-pkipko'},
|
|
178
|
+
'zip': {'application': 'zipped file', 'mine': 'application/zip'},
|
|
179
|
+
'xhtml': {'application': '', 'mine': 'application/xhtml+xml'}, # Added in 07 Mar, 2024
|
|
180
|
+
# MIME Types: Sound Files
|
|
181
|
+
'au': {'application': 'audio file', 'mine': 'audio/basic'},
|
|
182
|
+
'snd': {'application': 'sound file', 'mine': 'audio/basic'},
|
|
183
|
+
'mid': {'application': 'midi file', 'mine': 'audio/mid'},
|
|
184
|
+
'rmi': {'application': 'media processing server studio', 'mine': 'audio/mid'},
|
|
185
|
+
'mp3': {'application': 'MP3 file', 'mine': 'audio/mpeg'},
|
|
186
|
+
'aif': {'application': 'audio interchange file format', 'mine': 'audio/x-aiff'},
|
|
187
|
+
'aifc': {'application': 'compressed audio interchange file', 'mine': 'audio/x-aiff'},
|
|
188
|
+
'aiff': {'application': 'audio interchange file format', 'mine': 'audio/x-aiff'},
|
|
189
|
+
'm3u': {'application': 'media playlist file', 'mine': 'audio/x-mpegurl'},
|
|
190
|
+
'ra': {'application': 'Real Audio file', 'mine': 'audio/x-pn-realaudio'},
|
|
191
|
+
'ram': {'application': 'Real Audio metadata file', 'mine': 'audio/x-pn-realaudio'},
|
|
192
|
+
'wav': {'application': 'WAVE audio file', 'mine': 'audio/x-wav'},
|
|
193
|
+
# MIME Types: Image Files
|
|
194
|
+
'png': {'application': 'png', 'mine': 'image/png'},
|
|
195
|
+
'bmp': {'application': 'Bitmap', 'mine': 'image/bmp'},
|
|
196
|
+
'cod': {'application': 'compiled source code', 'mine': 'image/cis-cod'},
|
|
197
|
+
'gif': {'application': 'graphic interchange format', 'mine': 'image/gif'},
|
|
198
|
+
'ief': {'application': 'image file', 'mine': 'image/ief'},
|
|
199
|
+
'jpe': {'application': 'JPEG image', 'mine': 'image/jpeg'},
|
|
200
|
+
'jpeg': {'application': 'JPEG image', 'mine': 'image/jpeg'},
|
|
201
|
+
'jpg': {'application': 'JPEG image', 'mine': 'image/jpeg'},
|
|
202
|
+
'jfif': {'application': 'JPEG file interchange format', 'mine': 'image/pipeg'},
|
|
203
|
+
'svg': {'application': 'scalable vector graphic', 'mine': 'image/svg+xml'},
|
|
204
|
+
'tif': {'application': 'TIF image', 'mine': 'image/tiff'},
|
|
205
|
+
'tiff': {'application': 'TIF image', 'mine': 'image/tiff'},
|
|
206
|
+
'ras': {'application': 'Sun raster graphic', 'mine': 'image/x-cmu-raster'},
|
|
207
|
+
'cmx': {'application': 'Corel metafile exchange image file', 'mine': 'image/x-cmx'},
|
|
208
|
+
'ico': {'application': 'icon', 'mine': 'image/x-icon'},
|
|
209
|
+
'pnm': {'application': 'portable any map image', 'mine': 'image/x-portable-anymap'},
|
|
210
|
+
'pbm': {'application': 'portable bitmap image', 'mine': 'image/x-portable-bitmap'},
|
|
211
|
+
'pgm': {'application': 'portable graymap image', 'mine': 'image/x-portable-graymap'},
|
|
212
|
+
'ppm': {'application': 'portable pixmap image', 'mine': 'image/x-portable-pixmap'},
|
|
213
|
+
'rgb': {'application': 'RGB bitmap', 'mine': 'image/x-rgb'},
|
|
214
|
+
'xbm': {'application': 'X11 bitmap', 'mine': 'image/x-xbitmap'},
|
|
215
|
+
'xpm': {'application': 'X11 pixmap', 'mine': 'image/x-xpixmap'},
|
|
216
|
+
'xwd': {'application': 'X-Windows dump image', 'mine': 'image/x-xwindowdump'},
|
|
217
|
+
# MIME Types: Virtual World Files
|
|
218
|
+
'flr': {'application': 'Flare decompiled actionscript file', 'mine': 'x-world/x-vrml'},
|
|
219
|
+
'vrml': {'application': 'VRML file', 'mine': 'x-world/x-vrml'},
|
|
220
|
+
'wrl': {'application': 'VRML world', 'mine': 'x-world/x-vrml'},
|
|
221
|
+
'wrz': {'application': 'compressed VRML world', 'mine': 'x-world/x-vrml'},
|
|
222
|
+
'xaf': {'application': '3ds max XML animation file', 'mine': 'x-world/x-vrml'},
|
|
223
|
+
'xof': {'application': 'Reality Lab 3D image file', 'mine': 'x-world/x-vrml'},
|
|
224
|
+
# MIME Types: Video Files
|
|
225
|
+
'mp2': {'application': 'MPEG-2 audio file', 'mine': 'video/mpeg'},
|
|
226
|
+
'mpa': {'application': 'MPEG-2 audio file', 'mine': 'video/mpeg'},
|
|
227
|
+
'mpe': {'application': 'MPEG movie file', 'mine': 'video/mpeg'},
|
|
228
|
+
'mpeg': {'application': 'MPEG movie file', 'mine': 'video/mpeg'},
|
|
229
|
+
'mpg': {'application': 'MPEG movie file', 'mine': 'video/mpeg'},
|
|
230
|
+
'mpv2': {'application': 'MPEG-2 video stream', 'mine': 'video/mpeg'},
|
|
231
|
+
'mp4': {'application': 'MPEG-4', 'mine': 'video/mp4'},
|
|
232
|
+
'mov': {'application': 'Apple QuickTime movie', 'mine': 'video/quicktime'},
|
|
233
|
+
'qt': {'application': 'Apple QuickTime movie', 'mine': 'video/quicktime'},
|
|
234
|
+
'lsf': {'application': 'Logos library system file', 'mine': 'video/x-la-asf'},
|
|
235
|
+
'lsx': {'application': 'streaming media shortcut', 'mine': 'video/x-la-asf'},
|
|
236
|
+
'asf': {'application': 'advanced systems format file', 'mine': 'video/x-ms-asf'},
|
|
237
|
+
'asr': {'application': 'ActionScript remote document', 'mine': 'video/x-ms-asf'},
|
|
238
|
+
'asx': {'application': 'Microsoft ASF redirector file', 'mine': 'video/x-ms-asf'},
|
|
239
|
+
'avi': {'application': 'audio video interleave file', 'mine': 'video/x-msvideo'},
|
|
240
|
+
'movie': {'application': 'Apple QuickTime movie', 'mine': 'video/x-sgi-movie'},
|
|
241
|
+
# MIME Types: Text Files
|
|
242
|
+
'css': {'application': 'Cascading Style Sheet', 'mine': 'text/css'},
|
|
243
|
+
'323': {'application': 'H.323 internet telephony file', 'mine': 'text/h323'},
|
|
244
|
+
'htm': {'application': 'HTML file', 'mine': 'text/html'},
|
|
245
|
+
'html': {'application': 'HTML file', 'mine': 'text/html'},
|
|
246
|
+
'stm': {'application': 'Exchange streaming media file', 'mine': 'text/html'},
|
|
247
|
+
'uls': {'application': 'NetMeeting user location service file', 'mine': 'text/iuls'},
|
|
248
|
+
'bas': {'application': 'BASIC source code file', 'mine': 'text/plain'},
|
|
249
|
+
'c': {'application': 'C/C++ source code file', 'mine': 'text/plain'},
|
|
250
|
+
'h': {'application': 'C/C++/Objective C header file', 'mine': 'text/plain'},
|
|
251
|
+
'txt': {'application': 'text file', 'mine': 'text/plain'},
|
|
252
|
+
'rtx': {'application': 'rich text file', 'mine': 'text/richtext'},
|
|
253
|
+
'sct': {'application': 'Scitext continuous tone file', 'mine': 'text/scriptlet'},
|
|
254
|
+
'tsv': {'application': 'tab separated values file', 'mine': 'text/tab-separated-values'},
|
|
255
|
+
'htt': {'application': 'hypertext template file', 'mine': 'text/webviewhtml'},
|
|
256
|
+
'htc': {'application': 'HTML component file', 'mine': 'text/x-component'},
|
|
257
|
+
'etx': {'application': 'TeX font encoding file', 'mine': 'text/x-setext'},
|
|
258
|
+
'vcf': {'application': 'vCard file', 'mine': 'text/x-vcard'},
|
|
259
|
+
'csv': {'application': '', 'mine': 'text/csv'}, # Added in 07 Mar, 2024
|
|
260
|
+
# MIME Types: Mail Message Files
|
|
261
|
+
'mht': {'application': 'MHTML web archive', 'mine': 'message/rfc822'},
|
|
262
|
+
'mhtml': {'application': 'MIME HTML file', 'mine': 'message/rfc822'},
|
|
263
|
+
'nws': {'application': 'Windows Live Mail newsgroup file', 'mine': 'message/rfc822'},
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def get_content_type_v1(extension):
|
|
268
|
+
current_type = 'application/octet-stream'
|
|
269
|
+
|
|
270
|
+
try:
|
|
271
|
+
current_type = _content_file_mapping[extension]['mine']
|
|
272
|
+
except Exception as e:
|
|
273
|
+
pass
|
|
274
|
+
|
|
275
|
+
return current_type
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
class _SingletonType(type):
|
|
279
|
+
_instance_lock = threading.Lock()
|
|
280
|
+
|
|
281
|
+
def __call__(cls, *args, **kwargs):
|
|
282
|
+
if not hasattr(cls, "_instance"):
|
|
283
|
+
with _SingletonType._instance_lock:
|
|
284
|
+
if not hasattr(cls, "_instance"):
|
|
285
|
+
cls._instance = super(_SingletonType, cls).__call__(*args, **kwargs)
|
|
286
|
+
return cls._instance
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
class _SwitchContentFileType(metaclass=_SingletonType):
|
|
290
|
+
def __init__(self):
|
|
291
|
+
self.content2file_type_mapping = {}
|
|
292
|
+
self.file2content_type_mapping = {}
|
|
293
|
+
# To load all mapping data
|
|
294
|
+
for key, value in _content_file_mapping.items():
|
|
295
|
+
# print(key, value['mine'])
|
|
296
|
+
self.file2content_type_mapping[key] = value['mine']
|
|
297
|
+
if value['mine'] in self.content2file_type_mapping:
|
|
298
|
+
self.content2file_type_mapping[value['mine']].append(key)
|
|
299
|
+
else:
|
|
300
|
+
self.content2file_type_mapping[value['mine']] = [key]
|
|
301
|
+
|
|
302
|
+
def content2file_type(self, content_type):
|
|
303
|
+
current_type = ['*']
|
|
304
|
+
try:
|
|
305
|
+
current_type = self.content2file_type_mapping[str(content_type).strip().lower()]
|
|
306
|
+
except Exception as e:
|
|
307
|
+
pass
|
|
308
|
+
return current_type
|
|
309
|
+
|
|
310
|
+
def file2content_type(self, file_type):
|
|
311
|
+
current_type = 'application/octet-stream'
|
|
312
|
+
try:
|
|
313
|
+
current_type = self.file2content_type_mapping[str(file_type).strip().lower()]
|
|
314
|
+
except Exception as e:
|
|
315
|
+
pass
|
|
316
|
+
return current_type
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def content2file_type(content_type):
|
|
320
|
+
switch_content_file_type = _SwitchContentFileType()
|
|
321
|
+
return switch_content_file_type.content2file_type(content_type)
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def file2content_type(file_type):
|
|
325
|
+
switch_content_file_type = _SwitchContentFileType()
|
|
326
|
+
return switch_content_file_type.file2content_type(file_type)
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
# -------------------------------------------------------------------------------------------------------------
|
|
330
|
+
# Below are the recommended methods
|
|
331
|
+
# -------------------------------------------------------------------------------------------------------------
|
|
332
|
+
|
|
333
|
+
def get_content_type_4_filename(filename: str, text_with_utf8=False):
|
|
334
|
+
# If no '.' in filename then get the full filename instead.
|
|
335
|
+
file_extension = filename.split('.')[-1]
|
|
336
|
+
|
|
337
|
+
try:
|
|
338
|
+
extension = _content_file_mapping[file_extension.strip().lower()]['mine']
|
|
339
|
+
if text_with_utf8 and extension.startswith('text/'):
|
|
340
|
+
extension += ';charset=utf-8'
|
|
341
|
+
return extension
|
|
342
|
+
except:
|
|
343
|
+
return 'application/octet-stream'
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: enconia-utils
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: utils for enconia
|
|
5
|
+
Author-email: "Huanai Technology Co., Ltd" <zhou-jinyuan@foxmail.com>
|
|
6
|
+
License: BSD-3-Clause
|
|
7
|
+
Classifier: Development Status :: 3 - Alpha
|
|
8
|
+
Classifier: Intended Audience :: Developers
|
|
9
|
+
Classifier: License :: OSI Approved :: BSD License
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Requires-Python: >=3.9
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
License-File: LICENSE
|
|
17
|
+
Requires-Dist: requests>=2.25.0
|
|
18
|
+
Requires-Dist: pytz>=2021.1
|
|
19
|
+
Requires-Dist: deprecated>=1.2.0
|
|
20
|
+
Requires-Dist: oss2>=2.15.0
|
|
21
|
+
Requires-Dist: aliyun-log-python-sdk>=0.8.0
|
|
22
|
+
Requires-Dist: chinese_calendar>=1.6.0
|
|
23
|
+
Dynamic: license-file
|
|
24
|
+
|
|
25
|
+
# enconia-utils
|
|
26
|
+
|
|
27
|
+
Enconia 工具包,提供常用的 Python 工具函数集合。
|
|
28
|
+
|
|
29
|
+
## 安装
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install enconia-utils
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## 功能模块
|
|
36
|
+
|
|
37
|
+
### ID 生成器 (`id_generator`)
|
|
38
|
+
- `get_random_short_id()` - 生成 8 位随机短 ID
|
|
39
|
+
- `get_random_long_id()` - 生成 22 位随机长 ID
|
|
40
|
+
- `get_fix_short_id(base_str)` - 根据输入字符串生成固定的短 ID
|
|
41
|
+
- `get_fix_long_id(base_str)` - 根据输入字符串生成固定的长 ID
|
|
42
|
+
- `get_file_md5(filepath)` - 计算文件 MD5
|
|
43
|
+
|
|
44
|
+
### 日期工具 (`util_date`)
|
|
45
|
+
- `get_orbit_std_datetime()` - 获取标准格式的当前日期时间
|
|
46
|
+
- `get_orbit_std_date()` - 获取标准格式的当前日期
|
|
47
|
+
- `get_date_range_list_v2(start, end)` - 获取日期范围列表
|
|
48
|
+
- `get_next_day(day, interval)` - 获取下一天
|
|
49
|
+
- `get_next_workday_cn(day)` - 获取下一个中国工作日
|
|
50
|
+
|
|
51
|
+
### 飞书推送 (`feishu_push`)
|
|
52
|
+
- `FeiShuTalkChatBot` - 飞书机器人消息推送
|
|
53
|
+
|
|
54
|
+
### 阿里云 SLS 日志 (`aliyun_sls_log`)
|
|
55
|
+
- `send_log_to_sls()` - 发送日志到阿里云 SLS
|
|
56
|
+
|
|
57
|
+
### 阿里云 OSS (`util_oss`)
|
|
58
|
+
- `OssCloudObjectProvider` - OSS 文件操作(上传、下载、复制、删除等)
|
|
59
|
+
|
|
60
|
+
### 文件类型映射 (`util_type_mapping`)
|
|
61
|
+
- `get_content_type_4_filename(filename)` - 根据文件名获取 MIME 类型
|
|
62
|
+
- `file2content_type(ext)` - 扩展名转 MIME 类型
|
|
63
|
+
- `content2file_type(mime)` - MIME 类型转扩展名
|
|
64
|
+
|
|
65
|
+
## 依赖
|
|
66
|
+
|
|
67
|
+
- requests
|
|
68
|
+
- pytz
|
|
69
|
+
- deprecated
|
|
70
|
+
- oss2
|
|
71
|
+
- aliyun-log-python-sdk
|
|
72
|
+
- chinese_calendar
|
|
73
|
+
|
|
74
|
+
## License
|
|
75
|
+
|
|
76
|
+
BSD-3-Clause
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
enconia_utils/__init__.py,sha256=0WL5h1iPYVOdk64BMyLhzfDugzIkqfv9-dytW9YkapQ,1626
|
|
2
|
+
enconia_utils/aliyun_sls_log.py,sha256=GplzITXhIavmbuqY1DL723me2z1aai5JmOdOn_vWBzY,4654
|
|
3
|
+
enconia_utils/feishu_push.py,sha256=S1QWRPIImBuYLd4YXSDOFYa3Gnh1qFKybGjNfSVUefM,7089
|
|
4
|
+
enconia_utils/id_generator.py,sha256=nOhb8bVvjuLnHUkM1KVcuRtf1y5Q80Yj1iz4upPkioM,4036
|
|
5
|
+
enconia_utils/util_date.py,sha256=LRxr2UZ547M58OiMlsgx0nYVmqLF0ja0WvshvXiQE1Y,6361
|
|
6
|
+
enconia_utils/util_oss.py,sha256=-tSZTlmrv_TVaCRuRBvi6Qhbvk7ouuk1LWvc3ZBQZfU,4704
|
|
7
|
+
enconia_utils/util_type_mapping.py,sha256=4_BjV7DxqkJzwA2Wk6i4BVK7yVAgMNW8jfcORGJalsI,22821
|
|
8
|
+
enconia_utils-0.1.0.dist-info/licenses/LICENSE,sha256=rvK14ealwAT47qkAKUdC8oIuyMTtqFbBRNOPGY8xdGg,1534
|
|
9
|
+
enconia_utils-0.1.0.dist-info/METADATA,sha256=lHF6keCk0DCMPOX6TL3Ap2QdiOvMN8A2XRVf0Vrp1Ak,2387
|
|
10
|
+
enconia_utils-0.1.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
11
|
+
enconia_utils-0.1.0.dist-info/top_level.txt,sha256=UuvM6oHnoSut2ivfT3sDExg-jYYWZnZ44uJNAM-f6dY,14
|
|
12
|
+
enconia_utils-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026, Huanai Technology Co., Ltd.
|
|
4
|
+
All rights reserved.
|
|
5
|
+
|
|
6
|
+
Redistribution and use in source and binary forms, with or without
|
|
7
|
+
modification, are permitted provided that the following conditions are met:
|
|
8
|
+
|
|
9
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
10
|
+
list of conditions and the following disclaimer.
|
|
11
|
+
|
|
12
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
13
|
+
this list of conditions and the following disclaimer in the documentation
|
|
14
|
+
and/or other materials provided with the distribution.
|
|
15
|
+
|
|
16
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
17
|
+
contributors may be used to endorse or promote products derived from
|
|
18
|
+
this software without specific prior written permission.
|
|
19
|
+
|
|
20
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
21
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
22
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
23
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
24
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
25
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
26
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
27
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
28
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
29
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
enconia_utils
|