eric-tools 1.3.2__tar.gz → 1.3.3.1__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.
- {eric_tools-1.3.2 → eric_tools-1.3.3.1}/PKG-INFO +10 -6
- {eric_tools-1.3.2 → eric_tools-1.3.3.1}/README.md +9 -5
- {eric_tools-1.3.2 → eric_tools-1.3.3.1}/eric_tools/__init__.py +3 -4
- eric_tools-1.3.3.1/eric_tools/delete_duplicate.py +61 -0
- eric_tools-1.3.3.1/eric_tools/dynamic_settings.py +172 -0
- {eric_tools-1.3.2 → eric_tools-1.3.3.1}/eric_tools/jwt_encrypt.py +9 -6
- {eric_tools-1.3.2 → eric_tools-1.3.3.1}/eric_tools/send_email.py +9 -13
- {eric_tools-1.3.2 → eric_tools-1.3.3.1}/eric_tools.egg-info/PKG-INFO +10 -6
- {eric_tools-1.3.2 → eric_tools-1.3.3.1}/eric_tools.egg-info/SOURCES.txt +2 -0
- {eric_tools-1.3.2 → eric_tools-1.3.3.1}/setup.py +1 -1
- {eric_tools-1.3.2 → eric_tools-1.3.3.1}/LICENSE +0 -0
- {eric_tools-1.3.2 → eric_tools-1.3.3.1}/eric_tools/Abstract.py +0 -0
- {eric_tools-1.3.2 → eric_tools-1.3.3.1}/eric_tools/async_queue.py +0 -0
- {eric_tools-1.3.2 → eric_tools-1.3.3.1}/eric_tools/convert_json.py +0 -0
- {eric_tools-1.3.2 → eric_tools-1.3.3.1}/eric_tools/decorator.py +0 -0
- {eric_tools-1.3.2 → eric_tools-1.3.3.1}/eric_tools/downloader.py +0 -0
- {eric_tools-1.3.2 → eric_tools-1.3.3.1}/eric_tools/encryption_classmethod.py +0 -0
- {eric_tools-1.3.2 → eric_tools-1.3.3.1}/eric_tools/excel_generator.py +0 -0
- {eric_tools-1.3.2 → eric_tools-1.3.3.1}/eric_tools/exception_class.py +0 -0
- {eric_tools-1.3.2 → eric_tools-1.3.3.1}/eric_tools/ip.py +0 -0
- {eric_tools-1.3.2 → eric_tools-1.3.3.1}/eric_tools/logMixin.py +0 -0
- {eric_tools-1.3.2 → eric_tools-1.3.3.1}/eric_tools/logger.py +0 -0
- {eric_tools-1.3.2 → eric_tools-1.3.3.1}/eric_tools/nginx_log.py +0 -0
- {eric_tools-1.3.2 → eric_tools-1.3.3.1}/eric_tools/pgsql.py +0 -0
- {eric_tools-1.3.2 → eric_tools-1.3.3.1}/eric_tools/readconfig.py +0 -0
- {eric_tools-1.3.2 → eric_tools-1.3.3.1}/eric_tools/remove.py +0 -0
- {eric_tools-1.3.2 → eric_tools-1.3.3.1}/eric_tools/resize_image.py +0 -0
- {eric_tools-1.3.2 → eric_tools-1.3.3.1}/eric_tools/sftp.py +0 -0
- {eric_tools-1.3.2 → eric_tools-1.3.3.1}/eric_tools.egg-info/dependency_links.txt +0 -0
- {eric_tools-1.3.2 → eric_tools-1.3.3.1}/eric_tools.egg-info/requires.txt +0 -0
- {eric_tools-1.3.2 → eric_tools-1.3.3.1}/eric_tools.egg-info/top_level.txt +0 -0
- {eric_tools-1.3.2 → eric_tools-1.3.3.1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: eric_tools
|
3
|
-
Version: 1.3.
|
3
|
+
Version: 1.3.3.1
|
4
4
|
Summary: Python Daily Development Tools
|
5
5
|
Home-page: https://github.com/Eric-jxl/Tools
|
6
6
|
Author: Eric
|
@@ -16,8 +16,9 @@ License-File: LICENSE
|
|
16
16
|
|
17
17
|
# Tools
|
18
18
|
[](https://opensource.org/licenses/Apache-2.0)
|
19
|
-

|
20
20
|

|
21
|
+
[](https://github.com/eric-jxl/Tools/actions/workflows/publish-pypi.yml)
|
21
22
|
|
22
23
|
|
23
24
|
[Reids](https://eric-jxl.github.io/bak/index.html)
|
@@ -57,13 +58,16 @@ pip install eric_tools
|
|
57
58
|
|
58
59
|
>[!TIP]
|
59
60
|
>
|
60
|
-
> * async_queue.py
|
61
|
+
> * async_queue.py 异步队列操作
|
61
62
|
>
|
62
|
-
> * downloader.py
|
63
|
+
> * downloader.py 下载器
|
63
64
|
>
|
64
|
-
> * logMixIn.py
|
65
|
+
> * logMixIn.py 日志元类和高级日志装饰器
|
65
66
|
>
|
66
|
-
> * nginx_log.py
|
67
|
+
> * nginx_log.py nginx日志解析(默认access.log)
|
67
68
|
>
|
69
|
+
> * dynamic_settings 一个动态加载配置类(支持toml、yml、conf、csv、ini等)
|
70
|
+
>
|
71
|
+
> * delete_duplicate 删除工作区重复文件(包括图片等)
|
68
72
|
|
69
73
|
|
@@ -1,7 +1,8 @@
|
|
1
1
|
# Tools
|
2
2
|
[](https://opensource.org/licenses/Apache-2.0)
|
3
|
-

|
4
4
|

|
5
|
+
[](https://github.com/eric-jxl/Tools/actions/workflows/publish-pypi.yml)
|
5
6
|
|
6
7
|
|
7
8
|
[Reids](https://eric-jxl.github.io/bak/index.html)
|
@@ -41,11 +42,14 @@ pip install eric_tools
|
|
41
42
|
|
42
43
|
>[!TIP]
|
43
44
|
>
|
44
|
-
> * async_queue.py
|
45
|
+
> * async_queue.py 异步队列操作
|
45
46
|
>
|
46
|
-
> * downloader.py
|
47
|
+
> * downloader.py 下载器
|
47
48
|
>
|
48
|
-
> * logMixIn.py
|
49
|
+
> * logMixIn.py 日志元类和高级日志装饰器
|
49
50
|
>
|
50
|
-
> * nginx_log.py
|
51
|
+
> * nginx_log.py nginx日志解析(默认access.log)
|
51
52
|
>
|
53
|
+
> * dynamic_settings 一个动态加载配置类(支持toml、yml、conf、csv、ini等)
|
54
|
+
>
|
55
|
+
> * delete_duplicate 删除工作区重复文件(包括图片等)
|
@@ -4,15 +4,14 @@
|
|
4
4
|
@Time : 2020-11-30 17:31
|
5
5
|
@IDE : PyCharm
|
6
6
|
'''
|
7
|
-
|
8
|
-
from . import
|
9
|
-
from . import async_queue, downloader, logMixin, nginx_log, excel_generator
|
7
|
+
|
8
|
+
from .dynamic_settings import DynamicConfig
|
10
9
|
|
11
10
|
|
12
11
|
name = 'Eric-Tools'
|
13
12
|
__title__ = 'tools'
|
14
13
|
__description__ = 'Python HTTP for Humans.'
|
15
|
-
__version__ = "1.3.
|
14
|
+
__version__ = "1.3.3.1"
|
16
15
|
__author__ = 'Eric'
|
17
16
|
__doc__ = ["Python Daily Development Tools"]
|
18
17
|
__url__ = "https://github.com/Eric-jxl/Tools"
|
@@ -0,0 +1,61 @@
|
|
1
|
+
import os
|
2
|
+
import hashlib
|
3
|
+
import imagehash
|
4
|
+
from PIL import Image
|
5
|
+
|
6
|
+
|
7
|
+
def get_file_hash(file_path, is_image=False):
|
8
|
+
"""计算文件的哈希值(图片使用感知哈希,其他文件使用 SHA-256)"""
|
9
|
+
try:
|
10
|
+
if is_image:
|
11
|
+
with Image.open(file_path) as img:
|
12
|
+
return str(imagehash.phash(img)) # 感知哈希(pHash)
|
13
|
+
else:
|
14
|
+
hasher = hashlib.sha256()
|
15
|
+
with open(file_path, "rb") as f:
|
16
|
+
while chunk := f.read(8192): # 逐块读取,适用于大文件
|
17
|
+
hasher.update(chunk)
|
18
|
+
return hasher.hexdigest()
|
19
|
+
except Exception as e:
|
20
|
+
print(f"无法处理 {file_path},错误: {e}")
|
21
|
+
return None
|
22
|
+
|
23
|
+
|
24
|
+
def find_duplicates(folder_path):
|
25
|
+
"""查找并收集重复文件"""
|
26
|
+
hashes = {} # 存储 {哈希值: 文件路径}
|
27
|
+
duplicates = []
|
28
|
+
|
29
|
+
for root, _, files in os.walk(folder_path):
|
30
|
+
for file in files:
|
31
|
+
file_path = os.path.join(root, file)
|
32
|
+
ext = file.lower().split('.')[-1]
|
33
|
+
|
34
|
+
# 判断是否为图片
|
35
|
+
is_image = ext in ('png', 'jpg', 'jpeg', 'gif', 'bmp', 'webp')
|
36
|
+
|
37
|
+
file_hash = get_file_hash(file_path, is_image)
|
38
|
+
if file_hash:
|
39
|
+
if file_hash in hashes:
|
40
|
+
duplicates.append(file_path) # 记录重复项
|
41
|
+
else:
|
42
|
+
hashes[file_hash] = file_path
|
43
|
+
|
44
|
+
return duplicates
|
45
|
+
|
46
|
+
|
47
|
+
def remove_duplicates(duplicates):
|
48
|
+
"""删除重复文件"""
|
49
|
+
for dup in duplicates:
|
50
|
+
print(f"删除重复文件: {dup}")
|
51
|
+
os.remove(dup)
|
52
|
+
|
53
|
+
|
54
|
+
if __name__ == "__main__":
|
55
|
+
FOLDER_PATH = "/Users/eric/Downloads/Downloads/哔哩哔哩壁纸"
|
56
|
+
duplicates = find_duplicates(FOLDER_PATH)
|
57
|
+
if duplicates:
|
58
|
+
remove_duplicates(duplicates)
|
59
|
+
print(f"已删除 {len(duplicates)} 个重复文件!")
|
60
|
+
else:
|
61
|
+
print("未发现重复文件!")
|
@@ -0,0 +1,172 @@
|
|
1
|
+
import os
|
2
|
+
import yaml
|
3
|
+
import toml
|
4
|
+
import csv
|
5
|
+
import configparser
|
6
|
+
import logging
|
7
|
+
from collections.abc import MutableMapping
|
8
|
+
from watchdog.observers import Observer
|
9
|
+
from watchdog.events import FileSystemEventHandler
|
10
|
+
from flask import Flask, jsonify, redirect
|
11
|
+
|
12
|
+
logging.basicConfig(level=logging.INFO)
|
13
|
+
logger = logging.getLogger(__file__.rsplit("/", 1)[1])
|
14
|
+
|
15
|
+
__all__ = ["DynamicConfig"]
|
16
|
+
|
17
|
+
# Flask Web Service for testing
|
18
|
+
app = Flask(__name__)
|
19
|
+
|
20
|
+
|
21
|
+
class ConfigFileHandler(FileSystemEventHandler):
|
22
|
+
"""Handles file changes and reloads the configuration."""
|
23
|
+
|
24
|
+
def __init__(self, config, file_path):
|
25
|
+
self.config = config
|
26
|
+
self.file_path = file_path
|
27
|
+
|
28
|
+
def on_modified(self, event):
|
29
|
+
if event.src_path == self.file_path:
|
30
|
+
print(f"Configuration file {self.file_path} changed, reloading...")
|
31
|
+
self.config.force_reload(self.file_path)
|
32
|
+
|
33
|
+
|
34
|
+
class DynamicConfig(MutableMapping):
|
35
|
+
def __init__(self, watch_file=None, **kwargs):
|
36
|
+
self.watch_file = watch_file
|
37
|
+
self.config = kwargs # Support setting variables directly
|
38
|
+
|
39
|
+
if watch_file:
|
40
|
+
self.load(watch_file)
|
41
|
+
self._start_watching()
|
42
|
+
|
43
|
+
def _start_watching(self):
|
44
|
+
"""Starts a background thread to monitor file changes."""
|
45
|
+
observer = Observer()
|
46
|
+
observer.schedule(ConfigFileHandler(self, self.watch_file), path=os.path.dirname(
|
47
|
+
self.watch_file) or ".", recursive=False)
|
48
|
+
observer.start()
|
49
|
+
self._observer = observer
|
50
|
+
|
51
|
+
def load(self, file_path=None):
|
52
|
+
"""Loads configuration from a file and merges with existing values."""
|
53
|
+
if file_path:
|
54
|
+
self.file_path = file_path
|
55
|
+
|
56
|
+
if not self.file_path or not os.path.exists(self.file_path):
|
57
|
+
logger.error(f"Configuration file '{self.file_path}' not found.")
|
58
|
+
return
|
59
|
+
|
60
|
+
loaders = {
|
61
|
+
".yml": self._load_yaml, ".yaml": self._load_yaml,
|
62
|
+
".toml": self._load_toml, ".csv": self._load_csv,
|
63
|
+
".conf": self._load_conf, ".ini": self._load_conf
|
64
|
+
}
|
65
|
+
|
66
|
+
ext = os.path.splitext(self.file_path)[-1].lower()
|
67
|
+
if ext not in loaders:
|
68
|
+
raise ValueError(f"Unsupported file format: {ext}")
|
69
|
+
|
70
|
+
file_config = loaders[ext](self.file_path, self.config)
|
71
|
+
|
72
|
+
logger.debug("Updated Config:", self.config) # Debugging
|
73
|
+
|
74
|
+
def force_reload(self):
|
75
|
+
"""Manually trigger configuration reload (used in watchdog)."""
|
76
|
+
logger.info("Force reloading configuration...")
|
77
|
+
if self.watch_file:
|
78
|
+
self.load(self.watch_file)
|
79
|
+
|
80
|
+
def _load_yaml(self, file_path, config):
|
81
|
+
with open(file_path, 'r', encoding='utf-8') as f:
|
82
|
+
config.update(yaml.safe_load(f) or {})
|
83
|
+
|
84
|
+
def _load_toml(self, file_path, config):
|
85
|
+
with open(file_path, 'r', encoding='utf-8') as f:
|
86
|
+
config.update(toml.load(f))
|
87
|
+
|
88
|
+
def _load_csv(self, file_path, config):
|
89
|
+
with open(file_path, 'r', encoding='utf-8') as f:
|
90
|
+
reader = csv.reader(f)
|
91
|
+
config.update({row[0]: row[1] for row in reader if len(row) >= 2})
|
92
|
+
|
93
|
+
def _load_conf(self, file_path, config):
|
94
|
+
parser = configparser.ConfigParser()
|
95
|
+
parser.read(file_path, encoding='utf-8')
|
96
|
+
for section in parser.sections():
|
97
|
+
for key, value in parser.items(section):
|
98
|
+
config[f"{section}.{key}"] = value
|
99
|
+
|
100
|
+
def set(self, key, value):
|
101
|
+
"""Sets a configuration variable."""
|
102
|
+
self.config[key] = value
|
103
|
+
|
104
|
+
def get(self, key, default=None):
|
105
|
+
"""支持多层级 `.` 分隔键访问,如 `config.get("database.host")`"""
|
106
|
+
if key in self.config:
|
107
|
+
return self.config[key]
|
108
|
+
keys = key.split(".") # 按 `.` 分割多级键
|
109
|
+
value = self.config # 从 `self.config` 开始查找
|
110
|
+
|
111
|
+
try:
|
112
|
+
for k in keys:
|
113
|
+
if isinstance(value, dict): # 如果是字典,继续查找键
|
114
|
+
value = value[k]
|
115
|
+
elif isinstance(value, list) and k.isdigit(): # 如果是列表,并且 k 是索引
|
116
|
+
value = value[int(k)]
|
117
|
+
else:
|
118
|
+
return default # 找不到,返回默认值
|
119
|
+
return value
|
120
|
+
except (KeyError, IndexError, TypeError):
|
121
|
+
return default # 访问失败,返回默认值
|
122
|
+
|
123
|
+
def __getitem__(self, key):
|
124
|
+
return self.config[key]
|
125
|
+
|
126
|
+
def __setitem__(self, key, value):
|
127
|
+
self.config[key] = value
|
128
|
+
|
129
|
+
def __delitem__(self, key):
|
130
|
+
del self.config[key]
|
131
|
+
|
132
|
+
def __iter__(self):
|
133
|
+
return iter(self.config)
|
134
|
+
|
135
|
+
def __len__(self):
|
136
|
+
return len(self.config)
|
137
|
+
|
138
|
+
def __repr__(self):
|
139
|
+
return f"DynamicConfig({self.config})"
|
140
|
+
|
141
|
+
def stop_watching(self):
|
142
|
+
"""Stops the file watcher."""
|
143
|
+
if hasattr(self, "_observer"):
|
144
|
+
self._observer.stop()
|
145
|
+
self._observer.join()
|
146
|
+
|
147
|
+
|
148
|
+
@app.route("/", methods=["GET"])
|
149
|
+
def index():
|
150
|
+
return redirect("/config/")
|
151
|
+
|
152
|
+
|
153
|
+
@app.route("/config/", methods=["GET"])
|
154
|
+
def get_config():
|
155
|
+
config = DynamicConfig("config.yml", debug=True, mode="production")
|
156
|
+
config.force_reload()
|
157
|
+
return jsonify(config.config)
|
158
|
+
|
159
|
+
|
160
|
+
@app.route("/config/<path:key>", methods=["GET"]) # ✅ 允许 `/` 作为 key 的一部分
|
161
|
+
def get_config_key(key):
|
162
|
+
"""获取指定配置项的值,并支持自动重新加载"""
|
163
|
+
config = DynamicConfig("config.yml", debug=True, mode="production")
|
164
|
+
config.force_reload() # ✅ 先重新加载配置
|
165
|
+
value = Config.get(key) # ✅ 取值
|
166
|
+
if value is None:
|
167
|
+
return jsonify({"error": "Key not found"}), 404 # ✅ 404 + 友好错误信息
|
168
|
+
return jsonify(value)
|
169
|
+
|
170
|
+
|
171
|
+
if __name__ == "__main__":
|
172
|
+
app.run(host="0.0.0.0", port=5001, debug=True)
|
@@ -8,18 +8,21 @@
|
|
8
8
|
from jose.exceptions import ExpiredSignatureError, JWTError
|
9
9
|
from jose import jwt
|
10
10
|
import uuid
|
11
|
-
from datetime import datetime,timedelta
|
11
|
+
from datetime import datetime, timedelta
|
12
|
+
|
12
13
|
|
13
14
|
class GenerateAuthenticate(object):
|
14
|
-
def __init__(self,secretKey,cipher_text):
|
15
|
+
def __init__(self, secretKey, cipher_text):
|
15
16
|
self.secretKey = secretKey
|
16
17
|
self.cipher_text = cipher_text
|
17
|
-
super(GenerateAuthenticate,self).__init__(
|
18
|
+
super(GenerateAuthenticate, self).__init__(
|
19
|
+
secret_key=secretKey, cipher_text=cipher_text)
|
18
20
|
|
19
21
|
@staticmethod
|
20
22
|
def generate_access_token(SECRET_KEY, Plaintext):
|
21
23
|
expire = datetime.utcnow() + timedelta(minutes=1)
|
22
|
-
to_encode = {"exp": expire, "sub": str(
|
24
|
+
to_encode = {"exp": expire, "sub": str(
|
25
|
+
Plaintext), "uid": str(uuid.uuid4())}
|
23
26
|
token = jwt.encode(to_encode, SECRET_KEY, algorithm="HS256")
|
24
27
|
return token
|
25
28
|
|
@@ -29,7 +32,7 @@ class GenerateAuthenticate(object):
|
|
29
32
|
payload = jwt.decode(Ciphertext, secretKey, algorithms="HS256")
|
30
33
|
import sys
|
31
34
|
if isinstance(payload, unicode) or sys.version_info[0] < 3:
|
32
|
-
print
|
35
|
+
print(str(payload).encode('utf-8').replace('u\'', ''))
|
33
36
|
else:
|
34
37
|
print(payload)
|
35
38
|
return payload
|
@@ -37,4 +40,4 @@ class GenerateAuthenticate(object):
|
|
37
40
|
except ExpiredSignatureError:
|
38
41
|
print(u"token过期")
|
39
42
|
except JWTError:
|
40
|
-
print(u"token验证失败")
|
43
|
+
print(u"token验证失败")
|
@@ -10,26 +10,22 @@ __doc__ = ["测试QQ邮箱,pwd为邮箱授权码"]
|
|
10
10
|
import smtplib
|
11
11
|
from email.mime.text import MIMEText
|
12
12
|
from email.utils import formataddr
|
13
|
-
import sys
|
14
|
-
reload(sys)
|
15
|
-
sys.setdefaultencoding("utf-8")
|
16
13
|
|
17
14
|
|
18
|
-
|
19
|
-
|
20
|
-
def mail(send_email,receive_email,send_nickname,receive_nickname,pwd):
|
15
|
+
def mail(send_email, receive_email, send_nickname, receive_nickname, pwd):
|
21
16
|
try:
|
22
17
|
msg = MIMEText('Test QQ Email', 'plain', 'utf-8')
|
23
|
-
|
24
|
-
msg['
|
25
|
-
|
18
|
+
# 括号里的对应发件人邮箱昵称、发件人邮箱账号
|
19
|
+
msg['From'] = formataddr([send_nickname, send_email])
|
20
|
+
# 括号里的对应收件人邮箱昵称、收件人邮箱账号
|
21
|
+
msg['To'] = formataddr([receive_nickname, receive_email])
|
22
|
+
msg['Subject'] = "发送邮件测试" # 邮件的主题,也可以说是标题
|
26
23
|
|
27
24
|
server = smtplib.SMTP_SSL("smtp.qq.com", 465) # 发件人邮箱中的SMTP服务器,端口是25
|
28
25
|
server.login(send_email, pwd)
|
29
|
-
|
26
|
+
# 括号中对应的是发件人邮箱账号、收件人邮箱账号、发送邮件
|
27
|
+
server.sendmail(send_email, [receive_email, ], msg.as_string())
|
30
28
|
server.quit() # 关闭连接
|
31
29
|
except Exception as e:
|
32
|
-
print
|
30
|
+
print('发送邮件失败:%s' % e)
|
33
31
|
return e
|
34
|
-
|
35
|
-
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: eric-tools
|
3
|
-
Version: 1.3.
|
3
|
+
Version: 1.3.3.1
|
4
4
|
Summary: Python Daily Development Tools
|
5
5
|
Home-page: https://github.com/Eric-jxl/Tools
|
6
6
|
Author: Eric
|
@@ -16,8 +16,9 @@ License-File: LICENSE
|
|
16
16
|
|
17
17
|
# Tools
|
18
18
|
[](https://opensource.org/licenses/Apache-2.0)
|
19
|
-

|
20
20
|

|
21
|
+
[](https://github.com/eric-jxl/Tools/actions/workflows/publish-pypi.yml)
|
21
22
|
|
22
23
|
|
23
24
|
[Reids](https://eric-jxl.github.io/bak/index.html)
|
@@ -57,13 +58,16 @@ pip install eric_tools
|
|
57
58
|
|
58
59
|
>[!TIP]
|
59
60
|
>
|
60
|
-
> * async_queue.py
|
61
|
+
> * async_queue.py 异步队列操作
|
61
62
|
>
|
62
|
-
> * downloader.py
|
63
|
+
> * downloader.py 下载器
|
63
64
|
>
|
64
|
-
> * logMixIn.py
|
65
|
+
> * logMixIn.py 日志元类和高级日志装饰器
|
65
66
|
>
|
66
|
-
> * nginx_log.py
|
67
|
+
> * nginx_log.py nginx日志解析(默认access.log)
|
67
68
|
>
|
69
|
+
> * dynamic_settings 一个动态加载配置类(支持toml、yml、conf、csv、ini等)
|
70
|
+
>
|
71
|
+
> * delete_duplicate 删除工作区重复文件(包括图片等)
|
68
72
|
|
69
73
|
|
@@ -6,7 +6,9 @@ eric_tools/__init__.py
|
|
6
6
|
eric_tools/async_queue.py
|
7
7
|
eric_tools/convert_json.py
|
8
8
|
eric_tools/decorator.py
|
9
|
+
eric_tools/delete_duplicate.py
|
9
10
|
eric_tools/downloader.py
|
11
|
+
eric_tools/dynamic_settings.py
|
10
12
|
eric_tools/encryption_classmethod.py
|
11
13
|
eric_tools/excel_generator.py
|
12
14
|
eric_tools/exception_class.py
|
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
|
File without changes
|
File without changes
|