ezKit 1.7.6__tar.gz → 1.7.8__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.
- {ezkit-1.7.6/ezKit.egg-info → ezkit-1.7.8}/PKG-INFO +2 -1
- ezkit-1.7.8/README.md +60 -0
- ezkit-1.7.8/ezKit/__init__.py +0 -0
- {ezkit-1.7.6 → ezkit-1.7.8}/ezKit/cipher.py +11 -10
- {ezkit-1.7.6 → ezkit-1.7.8}/ezKit/database.py +11 -8
- {ezkit-1.7.6 → ezkit-1.7.8}/ezKit/mongo.py +24 -21
- {ezkit-1.7.6 → ezkit-1.7.8}/ezKit/qywx.py +12 -13
- {ezkit-1.7.6 → ezkit-1.7.8}/ezKit/redis.py +20 -18
- {ezkit-1.7.6 → ezkit-1.7.8}/ezKit/sendemail.py +1 -3
- ezkit-1.7.8/ezKit/token.py +77 -0
- {ezkit-1.7.6 → ezkit-1.7.8}/ezKit/utils.py +16 -7
- ezkit-1.7.8/ezKit/xftp.py +222 -0
- {ezkit-1.7.6 → ezkit-1.7.8/ezKit.egg-info}/PKG-INFO +2 -1
- {ezkit-1.7.6 → ezkit-1.7.8}/ezKit.egg-info/SOURCES.txt +1 -0
- ezkit-1.7.8/ezKit.egg-info/requires.txt +1 -0
- {ezkit-1.7.6 → ezkit-1.7.8}/setup.py +5 -6
- ezkit-1.7.6/README.md +0 -234
- ezkit-1.7.6/ezKit/__init__.py +0 -1
- ezkit-1.7.6/ezKit/token.py +0 -44
- ezkit-1.7.6/ezKit/xftp.py +0 -194
- {ezkit-1.7.6 → ezkit-1.7.8}/LICENSE +0 -0
- {ezkit-1.7.6 → ezkit-1.7.8}/MANIFEST.in +0 -0
- {ezkit-1.7.6 → ezkit-1.7.8}/ezKit/bottle.py +0 -0
- {ezkit-1.7.6 → ezkit-1.7.8}/ezKit/bottle_extensions.py +0 -0
- {ezkit-1.7.6 → ezkit-1.7.8}/ezKit/files.py +0 -0
- {ezkit-1.7.6 → ezkit-1.7.8}/ezKit/http.py +0 -0
- {ezkit-1.7.6 → ezkit-1.7.8}/ezKit/plots.py +0 -0
- {ezkit-1.7.6 → ezkit-1.7.8}/ezKit/reports.py +0 -0
- {ezkit-1.7.6 → ezkit-1.7.8}/ezKit/zabbix.py +0 -0
- {ezkit-1.7.6 → ezkit-1.7.8}/ezKit.egg-info/dependency_links.txt +0 -0
- {ezkit-1.7.6 → ezkit-1.7.8}/ezKit.egg-info/top_level.txt +0 -0
- {ezkit-1.7.6 → ezkit-1.7.8}/setup.cfg +0 -0
ezkit-1.7.8/README.md
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
# Python Easy Kit
|
2
|
+
|
3
|
+
## 代码规范
|
4
|
+
|
5
|
+
- [PEP 8 – Style Guide for Python Code](https://peps.python.org/pep-0008/)
|
6
|
+
- [PEP8 翻译](https://www.jianshu.com/p/78d76f85bd82)
|
7
|
+
- [PEP 8 -- Python 代码风格指南](https://github.com/kernellmd/Knowledge/blob/master/Translation/PEP%208%20%E4%B8%AD%E6%96%87%E7%BF%BB%E8%AF%91.md)
|
8
|
+
|
9
|
+
注释长度: 100
|
10
|
+
|
11
|
+
版本号说明: [PEP 440 – Version Identification and Dependency Specification](https://peps.python.org/pep-0440/)
|
12
|
+
|
13
|
+
配置文件格式: TOML
|
14
|
+
|
15
|
+
----------------------------------------------------------------------------------------------------
|
16
|
+
|
17
|
+
## 函数
|
18
|
+
|
19
|
+
- 明确 参数 的类型
|
20
|
+
- 尽量避免使用 *args 和 **kwargs
|
21
|
+
- 必须判断参数: 先判断参数是否为 None, 然后再判断参数类型
|
22
|
+
- 必须有返回值, 且明确返回值的类型(默认返回 bool 类型)
|
23
|
+
- 函数执行成功返回 True
|
24
|
+
- 执行执行失败返回 False
|
25
|
+
- 添加一个 debug 参数, 用于调试
|
26
|
+
- 如果 debug 为 True, 则输出相关信息, 否则一律不输出任何信息
|
27
|
+
- 必须有说明
|
28
|
+
- 必须用 `try ... except ...` 包裹
|
29
|
+
- 使用 loguru 输出信息 (不使用 print), 方便定位
|
30
|
+
|
31
|
+
```py
|
32
|
+
from loguru import logger
|
33
|
+
def func(debug: bool = False) -> bool:
|
34
|
+
try:
|
35
|
+
if debug is True:
|
36
|
+
logger.info("info ...")
|
37
|
+
# ...
|
38
|
+
return True
|
39
|
+
except Exception as e:
|
40
|
+
logger.exception(e)
|
41
|
+
return False
|
42
|
+
```
|
43
|
+
|
44
|
+
----------------------------------------------------------------------------------------------------
|
45
|
+
|
46
|
+
相关命令
|
47
|
+
|
48
|
+
```sh
|
49
|
+
# 打包
|
50
|
+
bash -x build.sh
|
51
|
+
|
52
|
+
# 上传
|
53
|
+
bash -x upload.sh
|
54
|
+
|
55
|
+
# 在线安装
|
56
|
+
pip install -U ezKit
|
57
|
+
|
58
|
+
# 离线安装
|
59
|
+
pip install -U ezKit-1.7.0.tar.gz
|
60
|
+
```
|
File without changes
|
@@ -1,9 +1,9 @@
|
|
1
|
-
|
1
|
+
"""
|
2
2
|
https://docs.python.org/3.10/library/hashlib.html
|
3
3
|
https://www.pycrypto.org/
|
4
4
|
https://stackoverflow.com/a/21928790
|
5
5
|
pip install pycryptodome
|
6
|
-
|
6
|
+
"""
|
7
7
|
import base64
|
8
8
|
import hashlib
|
9
9
|
|
@@ -12,7 +12,8 @@ from Crypto.Cipher import AES
|
|
12
12
|
from loguru import logger
|
13
13
|
|
14
14
|
|
15
|
-
class AESCipher
|
15
|
+
class AESCipher:
|
16
|
+
"""AESCipher"""
|
16
17
|
|
17
18
|
def __init__(self, key: str = 'vB7DoRm9C2Kd', algorithm: str = 'sha256'):
|
18
19
|
|
@@ -40,10 +41,10 @@ class AESCipher(object):
|
|
40
41
|
self.key = hashlib.sha3_384(key.encode()).digest()
|
41
42
|
case True if algorithm == 'sha3_512':
|
42
43
|
self.key = hashlib.sha3_512(key.encode()).digest()
|
43
|
-
case True if algorithm == 'shake_128':
|
44
|
-
|
45
|
-
case True if algorithm == 'shake_256':
|
46
|
-
|
44
|
+
# case True if algorithm == 'shake_128':
|
45
|
+
# self.key = hashlib.shake_128(key.encode()).digest()
|
46
|
+
# case True if algorithm == 'shake_256':
|
47
|
+
# self.key = hashlib.shake_256(key.encode()).digest()
|
47
48
|
case _:
|
48
49
|
self.key = hashlib.sha256(key.encode()).digest()
|
49
50
|
|
@@ -59,10 +60,10 @@ class AESCipher(object):
|
|
59
60
|
|
60
61
|
def decrypt(self, enc: str) -> str | None:
|
61
62
|
try:
|
62
|
-
|
63
|
-
iv =
|
63
|
+
enc_bytes = base64.b64decode(enc)
|
64
|
+
iv = enc_bytes[:AES.block_size]
|
64
65
|
cipher = AES.new(self.key, AES.MODE_CBC, iv)
|
65
|
-
return self._unpad(cipher.decrypt(
|
66
|
+
return self._unpad(cipher.decrypt(enc_bytes[AES.block_size:])).decode('utf-8')
|
66
67
|
except Exception as e:
|
67
68
|
logger.exception(e)
|
68
69
|
return None
|
@@ -14,13 +14,14 @@ from sqlalchemy import Index, create_engine, text
|
|
14
14
|
from . import utils
|
15
15
|
|
16
16
|
|
17
|
-
class Database(
|
17
|
+
class Database():
|
18
|
+
"""Database"""
|
18
19
|
|
19
20
|
engine = create_engine('sqlite://')
|
20
21
|
|
21
|
-
def __init__(self, engine_url
|
22
|
+
def __init__(self, engine_url, **engine_options):
|
22
23
|
'''Initiation'''
|
23
|
-
if engine_url
|
24
|
+
if engine_url is not None:
|
24
25
|
if utils.v_true(engine_options, dict):
|
25
26
|
self.engine = create_engine(engine_url, **engine_options)
|
26
27
|
else:
|
@@ -68,8 +69,8 @@ class Database(object):
|
|
68
69
|
idx = Index(index_name, table_field)
|
69
70
|
try:
|
70
71
|
idx.drop(bind=self.engine)
|
71
|
-
except:
|
72
|
-
|
72
|
+
except Exception as e:
|
73
|
+
logger.exception(e)
|
73
74
|
idx.create(bind=self.engine)
|
74
75
|
logger.success(f'{info}[成功]')
|
75
76
|
return True
|
@@ -86,7 +87,8 @@ class Database(object):
|
|
86
87
|
outcsv.writerows(data)
|
87
88
|
return True
|
88
89
|
except Exception as e:
|
89
|
-
|
90
|
+
if echo is True:
|
91
|
+
logger.exception(e)
|
90
92
|
return False
|
91
93
|
|
92
94
|
def execute(self, sql=None, sql_file=None, sql_file_kwargs=None, csv_file=None, csv_file_kwargs=None, echo=True):
|
@@ -104,12 +106,13 @@ class Database(object):
|
|
104
106
|
sql_object = None
|
105
107
|
info = f'{info_prefix}提取SQL'
|
106
108
|
try:
|
107
|
-
|
109
|
+
if echo is True:
|
110
|
+
logger.info(f'{info}......')
|
108
111
|
if utils.v_true(sql, str):
|
109
112
|
sql_object = sql
|
110
113
|
elif utils.v_true(sql_file, str):
|
111
114
|
# 判断文件是否存在
|
112
|
-
if utils.check_file_type(sql_file, 'file')
|
115
|
+
if utils.check_file_type(sql_file, 'file') is False:
|
113
116
|
logger.error(f'文件不存在: {sql_file}') if echo == True else next
|
114
117
|
return False
|
115
118
|
# 读取文件内容
|
@@ -1,6 +1,7 @@
|
|
1
1
|
"""MongoDB"""
|
2
2
|
from loguru import logger
|
3
3
|
from pymongo import MongoClient
|
4
|
+
from pymongo.collection import Collection
|
4
5
|
|
5
6
|
from . import utils
|
6
7
|
|
@@ -10,39 +11,41 @@ class Mongo():
|
|
10
11
|
|
11
12
|
client = MongoClient()
|
12
13
|
|
13
|
-
def close(self):
|
14
|
+
def close(self) -> bool:
|
14
15
|
"""client close"""
|
15
16
|
try:
|
16
17
|
self.client.close()
|
18
|
+
return True
|
17
19
|
except Exception as e:
|
18
20
|
logger.exception(e)
|
21
|
+
return False
|
19
22
|
|
20
|
-
def connect_test(self
|
21
|
-
"
|
22
|
-
info = 'MongoDB连接测试'
|
23
|
+
def connect_test(self) -> bool:
|
24
|
+
info = "MongoDB connect test"
|
23
25
|
try:
|
24
|
-
logger.info(f
|
26
|
+
logger.info(f"{info} ......")
|
25
27
|
self.client.server_info()
|
26
|
-
logger.success(f
|
28
|
+
logger.success(f"{info} [success]")
|
27
29
|
return True
|
28
30
|
except Exception as e:
|
29
|
-
logger.error(f
|
30
|
-
|
31
|
-
logger.exception(e)
|
31
|
+
logger.error(f"{info} [failed]")
|
32
|
+
logger.exception(e)
|
32
33
|
return False
|
33
34
|
|
34
|
-
def collection(self, database, name):
|
35
|
-
|
36
|
-
|
35
|
+
def collection(self, database: str, name: str) -> Collection | None:
|
36
|
+
try:
|
37
|
+
return self.client[database][name]
|
38
|
+
except Exception as e:
|
39
|
+
logger.exception(e)
|
40
|
+
return None
|
37
41
|
|
38
|
-
def collection_insert(self, database, collection, data, drop=
|
39
|
-
"""client collection insert"""
|
42
|
+
def collection_insert(self, database, collection, data, drop: bool = False):
|
40
43
|
db_collection = self.client[database][collection]
|
41
|
-
info =
|
44
|
+
info = "MongoDB collection insert"
|
42
45
|
try:
|
43
|
-
logger.info(f
|
46
|
+
logger.info(f"{info} ......")
|
44
47
|
# 是否删除 collection
|
45
|
-
if drop
|
48
|
+
if utils.v_true(drop, bool):
|
46
49
|
# 删除 collection
|
47
50
|
db_collection.drop()
|
48
51
|
# 插入数据
|
@@ -53,12 +56,12 @@ class Mongo():
|
|
53
56
|
# 插入多条数据
|
54
57
|
result = db_collection.insert_many(data)
|
55
58
|
else:
|
56
|
-
logger.error(f
|
57
|
-
logger.error(
|
59
|
+
logger.error(f"{info} [failed]")
|
60
|
+
logger.error("Data type error")
|
58
61
|
return False
|
59
|
-
logger.success(f
|
62
|
+
logger.success(f"{info} [success]")
|
60
63
|
return result
|
61
64
|
except Exception as e:
|
62
|
-
logger.error(f
|
65
|
+
logger.error(f"{info} [failed]")
|
63
66
|
logger.exception(e)
|
64
67
|
return False
|
@@ -1,3 +1,14 @@
|
|
1
|
+
"""
|
2
|
+
企业微信开发者中心
|
3
|
+
|
4
|
+
https://developer.work.weixin.qq.com/
|
5
|
+
https://developer.work.weixin.qq.com/document/path/90313 (全局错误码)
|
6
|
+
|
7
|
+
参考文档:
|
8
|
+
|
9
|
+
https://www.gaoyuanqi.cn/python-yingyong-qiyewx/
|
10
|
+
https://www.jianshu.com/p/020709b130d3
|
11
|
+
"""
|
1
12
|
import json
|
2
13
|
import time
|
3
14
|
|
@@ -7,21 +18,9 @@ from loguru import logger
|
|
7
18
|
from . import utils
|
8
19
|
|
9
20
|
|
10
|
-
class QYWX
|
21
|
+
class QYWX:
|
11
22
|
"""企业微信"""
|
12
23
|
|
13
|
-
"""
|
14
|
-
企业微信开发者中心
|
15
|
-
|
16
|
-
https://developer.work.weixin.qq.com/
|
17
|
-
https://developer.work.weixin.qq.com/document/path/90313 (全局错误码)
|
18
|
-
|
19
|
-
参考文档:
|
20
|
-
|
21
|
-
https://www.gaoyuanqi.cn/python-yingyong-qiyewx/
|
22
|
-
https://www.jianshu.com/p/020709b130d3
|
23
|
-
"""
|
24
|
-
|
25
24
|
url_prefix = 'https://qyapi.weixin.qq.com'
|
26
25
|
work_id: str | None = None
|
27
26
|
agent_id: str | None = None
|
@@ -1,10 +1,12 @@
|
|
1
|
+
"""Redis"""
|
1
2
|
import redis as RedisClient
|
2
3
|
from loguru import logger
|
3
4
|
|
4
5
|
from . import utils
|
5
6
|
|
6
7
|
|
7
|
-
class Redis
|
8
|
+
class Redis:
|
9
|
+
"""Redis"""
|
8
10
|
|
9
11
|
# https://redis.readthedocs.io/en/stable/_modules/redis/client.html#Redis
|
10
12
|
# https://github.com/redis/redis-py#client-classes-redis-and-strictredis
|
@@ -14,38 +16,38 @@ class Redis(object):
|
|
14
16
|
# 这里修改以下参数: host, port, socket_timeout, socket_connect_timeout, charset
|
15
17
|
redis = RedisClient.Redis()
|
16
18
|
|
17
|
-
def __init__(self, arguments
|
18
|
-
|
19
|
-
if utils.v_true(arguments, str):
|
19
|
+
def __init__(self, arguments: str | dict):
|
20
|
+
"""Initiation"""
|
21
|
+
if isinstance(arguments, str) and utils.v_true(arguments, str):
|
20
22
|
self.redis = RedisClient.from_url(arguments)
|
21
|
-
|
23
|
+
|
24
|
+
if isinstance(arguments, dict) and utils.v_true(arguments, dict):
|
22
25
|
self.redis = RedisClient.Redis(**arguments)
|
23
|
-
else:
|
24
|
-
pass
|
25
26
|
|
26
|
-
def connect_test(self):
|
27
|
-
info =
|
27
|
+
def connect_test(self) -> bool:
|
28
|
+
info = "Redis connect test"
|
28
29
|
try:
|
29
|
-
logger.info(f
|
30
|
+
logger.info(f"{info} ......")
|
30
31
|
self.redis.ping()
|
31
|
-
logger.success(f
|
32
|
+
logger.success(f"{info} [success]")
|
32
33
|
return True
|
33
34
|
except Exception as e:
|
34
|
-
logger.error(f
|
35
|
+
logger.error(f"{info} [failed]")
|
35
36
|
logger.exception(e)
|
36
37
|
return False
|
37
38
|
|
38
|
-
def flush(self,
|
39
|
-
info =
|
39
|
+
def flush(self, flushall: bool = False) -> bool:
|
40
|
+
info = "Redis flush"
|
40
41
|
try:
|
41
|
-
|
42
|
-
|
42
|
+
if utils.v_true(flushall, bool):
|
43
|
+
logger.info(f"{info} all ......")
|
43
44
|
self.redis.flushall()
|
44
45
|
else:
|
46
|
+
logger.info(f"{info} db ......")
|
45
47
|
self.redis.flushdb()
|
46
|
-
logger.success(f
|
48
|
+
logger.success(f"{info} [success]")
|
47
49
|
return True
|
48
50
|
except Exception as e:
|
49
|
-
logger.error(f
|
51
|
+
logger.error(f"{info} [failed]")
|
50
52
|
logger.exception(e)
|
51
53
|
return False
|
@@ -0,0 +1,77 @@
|
|
1
|
+
"""Token"""
|
2
|
+
import json
|
3
|
+
from typing import Any
|
4
|
+
|
5
|
+
from loguru import logger
|
6
|
+
|
7
|
+
from . import cipher, utils
|
8
|
+
|
9
|
+
|
10
|
+
def generate_token(key: str = 'Fc0zXCmGKd7tPu6W', timeout: int = 3600, data: Any = None) -> (str | None):
|
11
|
+
try:
|
12
|
+
now = utils.datetime_now()
|
13
|
+
|
14
|
+
if now is None:
|
15
|
+
return None
|
16
|
+
|
17
|
+
offset = utils.datetime_offset(now, seconds=+timeout)
|
18
|
+
|
19
|
+
if offset is None:
|
20
|
+
return None
|
21
|
+
|
22
|
+
source = json.dumps(
|
23
|
+
obj={
|
24
|
+
"datetime": utils.datetime_to_string(offset),
|
25
|
+
"data": data
|
26
|
+
},
|
27
|
+
default=str
|
28
|
+
)
|
29
|
+
|
30
|
+
aes_cipher = cipher.AESCipher(key=key, algorithm='sha256')
|
31
|
+
|
32
|
+
return aes_cipher.encrypt(source)
|
33
|
+
|
34
|
+
except Exception as e:
|
35
|
+
logger.exception(e)
|
36
|
+
return None
|
37
|
+
|
38
|
+
|
39
|
+
def parsing_token(token_string: str, key: str = 'Fc0zXCmGKd7tPu6W') -> (dict | None):
|
40
|
+
try:
|
41
|
+
if utils.v_true(token_string, str) is False:
|
42
|
+
return None
|
43
|
+
|
44
|
+
aes_cipher = cipher.AESCipher(key=key, algorithm='sha256')
|
45
|
+
|
46
|
+
target = aes_cipher.decrypt(token_string)
|
47
|
+
|
48
|
+
if target is None:
|
49
|
+
return None
|
50
|
+
|
51
|
+
source: dict = json.loads(target)
|
52
|
+
|
53
|
+
source['datetime'] = utils.datetime_string_to_datetime(source['datetime'])
|
54
|
+
|
55
|
+
return source
|
56
|
+
|
57
|
+
except Exception as e:
|
58
|
+
logger.exception(e)
|
59
|
+
return None
|
60
|
+
|
61
|
+
|
62
|
+
def certify_token(token_string: str, key: str = 'Fc0zXCmGKd7tPu6W') -> bool:
|
63
|
+
try:
|
64
|
+
|
65
|
+
result = parsing_token(token_string, key)
|
66
|
+
|
67
|
+
if result is None:
|
68
|
+
return False
|
69
|
+
|
70
|
+
if result.get('datetime') < utils.datetime_now(): # type: ignore
|
71
|
+
return False
|
72
|
+
|
73
|
+
return True
|
74
|
+
|
75
|
+
except Exception as e:
|
76
|
+
logger.exception(e)
|
77
|
+
return False
|
@@ -1,6 +1,4 @@
|
|
1
|
-
"""
|
2
|
-
Python Utils
|
3
|
-
"""
|
1
|
+
"""Utils"""
|
4
2
|
import csv
|
5
3
|
import datetime
|
6
4
|
import hashlib
|
@@ -49,6 +47,10 @@ def v_true(
|
|
49
47
|
List list/tuple/set []/()/{}
|
50
48
|
Dictionary dict {}
|
51
49
|
|
50
|
+
查看变量类型: type(x)
|
51
|
+
|
52
|
+
判断变量类型: isinstance(x, str)
|
53
|
+
|
52
54
|
函数使用 callable(func) 判断
|
53
55
|
"""
|
54
56
|
|
@@ -623,7 +625,7 @@ def dict_remove_key(
|
|
623
625
|
data: dict,
|
624
626
|
key: str,
|
625
627
|
debug: bool = False
|
626
|
-
) ->
|
628
|
+
) -> dict | None:
|
627
629
|
"""dict remove key"""
|
628
630
|
try:
|
629
631
|
data_copy: dict = deepcopy(data)
|
@@ -984,7 +986,9 @@ def datetime_now(
|
|
984
986
|
"""获取当前日期和时间"""
|
985
987
|
_utc = kwargs.pop("utc", False)
|
986
988
|
try:
|
987
|
-
|
989
|
+
if _utc is True:
|
990
|
+
return datetime.datetime.now(datetime.timezone.utc)
|
991
|
+
return datetime.datetime.now(**kwargs)
|
988
992
|
except Exception as e:
|
989
993
|
if v_true(debug, bool):
|
990
994
|
logger.exception(e)
|
@@ -992,7 +996,7 @@ def datetime_now(
|
|
992
996
|
|
993
997
|
|
994
998
|
def datetime_offset(
|
995
|
-
datetime_instance: datetime.datetime,
|
999
|
+
datetime_instance: datetime.datetime | None = None,
|
996
1000
|
debug: bool = False,
|
997
1001
|
**kwargs
|
998
1002
|
) -> datetime.datetime | None:
|
@@ -1005,7 +1009,12 @@ def datetime_offset(
|
|
1005
1009
|
try:
|
1006
1010
|
if isinstance(datetime_instance, datetime.datetime):
|
1007
1011
|
return datetime_instance + datetime.timedelta(**kwargs)
|
1008
|
-
|
1012
|
+
|
1013
|
+
if _utc is True:
|
1014
|
+
return datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(**kwargs)
|
1015
|
+
|
1016
|
+
return datetime.datetime.now() + datetime.timedelta(**kwargs)
|
1017
|
+
|
1009
1018
|
except Exception as e:
|
1010
1019
|
if v_true(debug, bool):
|
1011
1020
|
logger.exception(e)
|
@@ -0,0 +1,222 @@
|
|
1
|
+
"""
|
2
|
+
ftplib: https://docs.python.org/3.11/library/ftplib.html
|
3
|
+
"""
|
4
|
+
import os
|
5
|
+
from ftplib import FTP
|
6
|
+
from pathlib import Path
|
7
|
+
|
8
|
+
from loguru import logger
|
9
|
+
|
10
|
+
|
11
|
+
class XFTP:
|
12
|
+
"""XFTP"""
|
13
|
+
|
14
|
+
def __init__(self, host="127.0.0.1", port=21, username="anonymous", password="", encoding="UTF-8", debuglevel=0):
|
15
|
+
"""Initiation"""
|
16
|
+
self.ftp = FTP()
|
17
|
+
self.ftp.set_debuglevel(debuglevel)
|
18
|
+
self.host = host
|
19
|
+
self.port = port
|
20
|
+
self.username = username
|
21
|
+
self.password = password
|
22
|
+
self.encoding = encoding
|
23
|
+
self.retry = 1
|
24
|
+
|
25
|
+
def connect(self) -> bool:
|
26
|
+
"""FTP connect"""
|
27
|
+
try:
|
28
|
+
self.ftp.connect(host=self.host, port=self.port, timeout=10)
|
29
|
+
self.ftp.encoding = self.encoding
|
30
|
+
self.ftp.login(user=self.username, passwd=self.password)
|
31
|
+
logger.success("FTP connect success")
|
32
|
+
logger.info("-" * 80)
|
33
|
+
return True
|
34
|
+
except Exception as e:
|
35
|
+
# print(f"FTP connect error: {e}, retry...")
|
36
|
+
# if self.retry >= 3:
|
37
|
+
# print("FTP connect faild")
|
38
|
+
# return False
|
39
|
+
# self.retry += 1
|
40
|
+
# self.connect()
|
41
|
+
logger.exception(e)
|
42
|
+
return False
|
43
|
+
|
44
|
+
def close(self, info=None) -> bool:
|
45
|
+
"""FTP close"""
|
46
|
+
if info is not None:
|
47
|
+
logger.info(info)
|
48
|
+
try:
|
49
|
+
self.ftp.quit()
|
50
|
+
except Exception as e:
|
51
|
+
logger.exception(e)
|
52
|
+
self.ftp.close()
|
53
|
+
logger.info("-" * 80)
|
54
|
+
logger.success("FTP connect closed")
|
55
|
+
return True
|
56
|
+
|
57
|
+
def get_file_list(self, target='/') -> list[str] | None:
|
58
|
+
"""Get file list"""
|
59
|
+
try:
|
60
|
+
self.chdir_to_remote(target)
|
61
|
+
return self.ftp.nlst()
|
62
|
+
except Exception as e:
|
63
|
+
logger.exception(e)
|
64
|
+
return None
|
65
|
+
|
66
|
+
def get_file_size(self, file, target="/") -> int | None:
|
67
|
+
"""Get file size"""
|
68
|
+
try:
|
69
|
+
self.chdir_to_remote(target)
|
70
|
+
return self.ftp.size(file)
|
71
|
+
except Exception as e:
|
72
|
+
logger.exception(e)
|
73
|
+
return None
|
74
|
+
|
75
|
+
def mkdir(self, target="/") -> bool:
|
76
|
+
"""创建目录 (从 / 目录依次递增创建子目录. 如果目录存在, 创建目录时会报错, 所以这里忽略所有错误.)"""
|
77
|
+
try:
|
78
|
+
dir_list = target.split("/")
|
79
|
+
for i, _ in enumerate(dir_list):
|
80
|
+
dir_path = "/".join(dir_list[:i + 1])
|
81
|
+
try:
|
82
|
+
self.ftp.mkd(dir_path)
|
83
|
+
except Exception as e:
|
84
|
+
logger.exception(e)
|
85
|
+
return True
|
86
|
+
except Exception as e:
|
87
|
+
logger.exception(e)
|
88
|
+
return False
|
89
|
+
|
90
|
+
def chdir_to_remote(self, target="/") -> bool:
|
91
|
+
"""change to remote directory"""
|
92
|
+
try:
|
93
|
+
self.ftp.cwd(target)
|
94
|
+
return True
|
95
|
+
except Exception as e:
|
96
|
+
self.close(f"remote directory error: {target}")
|
97
|
+
logger.exception(e)
|
98
|
+
return False
|
99
|
+
|
100
|
+
def x_exit(self, info=None):
|
101
|
+
"""Exit"""
|
102
|
+
if info is not None:
|
103
|
+
logger.info(info)
|
104
|
+
# 注意: exit() 并不会退出脚本, 配合 try 使用
|
105
|
+
exit()
|
106
|
+
|
107
|
+
def x_exec(self, local_dir=".", local_file="", remote_dir="/", remote_file="", upload=False):
|
108
|
+
"""Download or Upload"""
|
109
|
+
|
110
|
+
bufsize = 1024
|
111
|
+
local_path = f"{local_dir}/{local_file}"
|
112
|
+
remote_path = f"{remote_dir}/{remote_file}"
|
113
|
+
|
114
|
+
info = 'Download'
|
115
|
+
if upload is True:
|
116
|
+
info = 'Upload'
|
117
|
+
|
118
|
+
# 检查参数
|
119
|
+
if upload is True:
|
120
|
+
if local_file == "":
|
121
|
+
self.close('Argument Miss: local file')
|
122
|
+
# 如果没有设置 远程文件 名称, 则使用 本地文件 名称
|
123
|
+
if remote_file == "":
|
124
|
+
remote_file = local_file
|
125
|
+
remote_path = f"{remote_dir}/{remote_file}"
|
126
|
+
else:
|
127
|
+
if remote_file == "":
|
128
|
+
self.close("Argument Miss: remote file")
|
129
|
+
# 如果没有设置 本地文件 名称, 则使用 远程文件 名称
|
130
|
+
if local_file == "":
|
131
|
+
local_file = remote_file
|
132
|
+
local_path = f"{local_dir}/{local_file}"
|
133
|
+
|
134
|
+
# 进入本地目录
|
135
|
+
try:
|
136
|
+
if upload is True:
|
137
|
+
# 检查本地目录
|
138
|
+
stat = Path(local_dir)
|
139
|
+
if stat.exists() is False:
|
140
|
+
self.close(f"Local directory error: {local_dir}")
|
141
|
+
else:
|
142
|
+
# 创建本地目录
|
143
|
+
Path(local_dir).mkdir(parents=True, exist_ok=True)
|
144
|
+
# 进入本地目录
|
145
|
+
os.chdir(local_dir)
|
146
|
+
except Exception as e:
|
147
|
+
logger.exception(e)
|
148
|
+
# 第一层 try 使用 self.x_exit() 无效, 直接使用 self.close()
|
149
|
+
self.close(f"Local directory error: {local_dir}")
|
150
|
+
|
151
|
+
# 上传或下载
|
152
|
+
try:
|
153
|
+
|
154
|
+
if upload is True:
|
155
|
+
|
156
|
+
# 上传
|
157
|
+
|
158
|
+
# 创建远程目录
|
159
|
+
if remote_dir != "/":
|
160
|
+
self.mkdir(remote_dir)
|
161
|
+
|
162
|
+
# 进入远程目录
|
163
|
+
self.chdir_to_remote(remote_dir)
|
164
|
+
|
165
|
+
# 上传文件
|
166
|
+
stat = Path(local_file)
|
167
|
+
if stat.exists() and stat.is_file():
|
168
|
+
with open(local_file, "rb") as fid:
|
169
|
+
self.ftp.storbinary(f'STOR {remote_file}', fid, bufsize)
|
170
|
+
logger.success(f"{info} success: {local_path.replace('//', '/')} -> {remote_path.replace('//', '/')}")
|
171
|
+
return True
|
172
|
+
|
173
|
+
self.x_exit(f"{info} error: {local_path.replace('//', '/')} is not exist")
|
174
|
+
|
175
|
+
else:
|
176
|
+
|
177
|
+
# 下载
|
178
|
+
|
179
|
+
# 进入远程目录
|
180
|
+
self.chdir_to_remote(remote_dir)
|
181
|
+
|
182
|
+
# 下载文件
|
183
|
+
if remote_file in self.ftp.nlst():
|
184
|
+
with open(local_file, "wb") as fid:
|
185
|
+
self.ftp.retrbinary(f'RETR {remote_file}', fid.write, bufsize)
|
186
|
+
logger.success(f"{info} success: {remote_path.replace('//', '/')} -> {local_path.replace('//', '/')}")
|
187
|
+
return True
|
188
|
+
|
189
|
+
self.x_exit(f"{info} error: {remote_path.replace('//', '/')} is not exist")
|
190
|
+
|
191
|
+
except Exception as e:
|
192
|
+
# 第一层 try 使用 self.x_exit() 无效, 直接使用 self.close()
|
193
|
+
# self.close('{} faild! Please check {} or {}'.format(info, local_path, remote_path))
|
194
|
+
self.close(f"{info} error: {e}")
|
195
|
+
return False
|
196
|
+
|
197
|
+
def handle_all(self, local_dir=".", remote_dir="/", upload=False):
|
198
|
+
"""Handle All"""
|
199
|
+
if upload is True:
|
200
|
+
# 检查本地目录
|
201
|
+
stat = Path(local_dir)
|
202
|
+
if stat.exists() is False:
|
203
|
+
self.close(f"Local directory error: {local_dir}")
|
204
|
+
# 获取文件列表
|
205
|
+
local_files = [f for f in os.listdir(local_dir) if os.path.isfile(os.path.join(local_dir, f))]
|
206
|
+
for i in local_files:
|
207
|
+
self.x_exec(local_dir=local_dir, remote_dir=remote_dir, local_file=i, upload=True)
|
208
|
+
else:
|
209
|
+
remote_files = self.get_file_list(remote_dir)
|
210
|
+
if remote_files is not None:
|
211
|
+
for i in remote_files:
|
212
|
+
self.x_exec(local_dir=local_dir, remote_dir=remote_dir, remote_file=i)
|
213
|
+
|
214
|
+
def retrlines(self, remote_dir="/", cmd="LIST"):
|
215
|
+
"""Retrlines"""
|
216
|
+
try:
|
217
|
+
self.chdir_to_remote(remote_dir)
|
218
|
+
print(self.ftp.retrlines(cmd))
|
219
|
+
self.close()
|
220
|
+
except Exception as e:
|
221
|
+
# 第一层 try 使用 self.x_exit() 无效, 直接使用 self.close()
|
222
|
+
self.close(e)
|
@@ -0,0 +1 @@
|
|
1
|
+
loguru>=0.7.0
|
@@ -3,15 +3,14 @@ from setuptools import find_packages, setup
|
|
3
3
|
|
4
4
|
setup(
|
5
5
|
name='ezKit',
|
6
|
-
version='1.7.
|
6
|
+
version='1.7.8',
|
7
7
|
author='septvean',
|
8
8
|
author_email='septvean@gmail.com',
|
9
9
|
description='Easy Kit',
|
10
10
|
packages=find_packages(exclude=['documents', 'tests']),
|
11
11
|
include_package_data=True,
|
12
|
-
python_requires='>=3.11'
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
# ]
|
12
|
+
python_requires='>=3.11',
|
13
|
+
install_requires=[
|
14
|
+
"loguru>=0.7.0"
|
15
|
+
]
|
17
16
|
)
|
ezkit-1.7.6/README.md
DELETED
@@ -1,234 +0,0 @@
|
|
1
|
-
# Python Easy Kit
|
2
|
-
|
3
|
-
保留关键字:
|
4
|
-
|
5
|
-
- <https://docs.python.org/3.10/reference/lexical_analysis.html#keywords>
|
6
|
-
|
7
|
-
```py
|
8
|
-
import keyword
|
9
|
-
keyword.kwlist
|
10
|
-
|
11
|
-
"""
|
12
|
-
[
|
13
|
-
'False', 'None', 'True', 'and', 'as', 'assert', 'async', 'await', 'break', 'class', 'continue',
|
14
|
-
'def', 'del', 'elif', 'else', 'except', 'finally', 'for', 'from', 'global', 'if', 'import', 'in', 'is',
|
15
|
-
'lambda', 'nonlocal', 'not', 'or', 'pass', 'raise', 'return', 'try', 'while', 'with', 'yield'
|
16
|
-
]
|
17
|
-
```
|
18
|
-
|
19
|
-
内置函数:
|
20
|
-
|
21
|
-
- <https://docs.python.org/3.10/library/functions.html>
|
22
|
-
|
23
|
-
Built-in Functions
|
24
|
-
|
25
|
-
```py
|
26
|
-
# A
|
27
|
-
abs()
|
28
|
-
aiter()
|
29
|
-
all()
|
30
|
-
any()
|
31
|
-
anext()
|
32
|
-
ascii()
|
33
|
-
|
34
|
-
# B
|
35
|
-
bin()
|
36
|
-
bool()
|
37
|
-
breakpoint()
|
38
|
-
bytearray()
|
39
|
-
bytes()
|
40
|
-
|
41
|
-
# C
|
42
|
-
callable()
|
43
|
-
chr()
|
44
|
-
classmethod()
|
45
|
-
compile()
|
46
|
-
complex()
|
47
|
-
|
48
|
-
# D
|
49
|
-
delattr()
|
50
|
-
dict()
|
51
|
-
dir()
|
52
|
-
divmod()
|
53
|
-
|
54
|
-
# E
|
55
|
-
enumerate()
|
56
|
-
eval()
|
57
|
-
exec()
|
58
|
-
|
59
|
-
# F
|
60
|
-
filter()
|
61
|
-
float()
|
62
|
-
format()
|
63
|
-
frozenset()
|
64
|
-
|
65
|
-
# G
|
66
|
-
getattr()
|
67
|
-
globals()
|
68
|
-
|
69
|
-
# H
|
70
|
-
hasattr()
|
71
|
-
hash()
|
72
|
-
help()
|
73
|
-
hex()
|
74
|
-
|
75
|
-
# I
|
76
|
-
id()
|
77
|
-
input()
|
78
|
-
int()
|
79
|
-
isinstance()
|
80
|
-
issubclass()
|
81
|
-
iter()
|
82
|
-
|
83
|
-
# L
|
84
|
-
len()
|
85
|
-
list()
|
86
|
-
locals()
|
87
|
-
|
88
|
-
# M
|
89
|
-
map()
|
90
|
-
max()
|
91
|
-
memoryview()
|
92
|
-
min()
|
93
|
-
|
94
|
-
# N
|
95
|
-
next()
|
96
|
-
|
97
|
-
# O
|
98
|
-
object()
|
99
|
-
oct()
|
100
|
-
open()
|
101
|
-
ord()
|
102
|
-
|
103
|
-
# P
|
104
|
-
pow()
|
105
|
-
print()
|
106
|
-
property()
|
107
|
-
|
108
|
-
# R
|
109
|
-
range()
|
110
|
-
repr()
|
111
|
-
reversed()
|
112
|
-
round()
|
113
|
-
|
114
|
-
# S
|
115
|
-
set()
|
116
|
-
setattr()
|
117
|
-
slice()
|
118
|
-
sorted()
|
119
|
-
staticmethod()
|
120
|
-
str()
|
121
|
-
sum()
|
122
|
-
super()
|
123
|
-
|
124
|
-
# T
|
125
|
-
tuple()
|
126
|
-
type()
|
127
|
-
|
128
|
-
# V
|
129
|
-
vars()
|
130
|
-
|
131
|
-
# Z
|
132
|
-
zip()
|
133
|
-
|
134
|
-
# _
|
135
|
-
__import__()
|
136
|
-
```
|
137
|
-
|
138
|
-
代码规范:
|
139
|
-
|
140
|
-
- [PEP 8 – Style Guide for Python Code](https://peps.python.org/pep-0008/)
|
141
|
-
- [PEP8 翻译](https://www.jianshu.com/p/78d76f85bd82)
|
142
|
-
- [PEP 8 -- Python 代码风格指南](https://github.com/kernellmd/Knowledge/blob/master/Translation/PEP%208%20%E4%B8%AD%E6%96%87%E7%BF%BB%E8%AF%91.md)
|
143
|
-
|
144
|
-
注释长度: 100
|
145
|
-
|
146
|
-
手动安装:
|
147
|
-
|
148
|
-
```sh
|
149
|
-
pip install ezKit-1.1.3.tar.gz
|
150
|
-
```
|
151
|
-
|
152
|
-
版本号说明: [PEP 440 – Version Identification and Dependency Specification](https://peps.python.org/pep-0440/)
|
153
|
-
|
154
|
-
## 函数
|
155
|
-
|
156
|
-
- 明确 参数 和 返回值 的类型
|
157
|
-
- 必须有返回值
|
158
|
-
- 添加一个 debug 参数, 用于调试
|
159
|
-
- 如果 debug 为 True, 则输出相关信息, 否则一律不输出任何信息
|
160
|
-
- 必须有说明
|
161
|
-
- 必须用 try ... except ... 包裹
|
162
|
-
|
163
|
-
try ... except ... 一律输出 Exception:
|
164
|
-
|
165
|
-
```py
|
166
|
-
def func():
|
167
|
-
try:
|
168
|
-
...
|
169
|
-
except Exception as e:
|
170
|
-
logger.exception(e)
|
171
|
-
return None
|
172
|
-
```
|
173
|
-
|
174
|
-
Boolen (False)
|
175
|
-
|
176
|
-
| Types | False |
|
177
|
-
| --- | --- |
|
178
|
-
| bool | False |
|
179
|
-
| int | 0 |
|
180
|
-
| float | 0.0 |
|
181
|
-
| str | '' |
|
182
|
-
| list | [] |
|
183
|
-
| tuple | () |
|
184
|
-
| dict | {} |
|
185
|
-
| set | {\*()} {\*[]} {\*{}} |
|
186
|
-
|
187
|
-
list/tuple/dict/set 初始化和类型转换:
|
188
|
-
|
189
|
-
- 变量初始化推荐使用 `[]/()/{}/{*()}/{*[]}/{*{}}` (性能更好)
|
190
|
-
- 类型转换则使用具体函数 `list()/tuple()/dict()/set()`
|
191
|
-
|
192
|
-
list/tuple/set 的区别:
|
193
|
-
|
194
|
-
- list 元素可以改变且可以不唯一
|
195
|
-
- tuple 元素不能改变且可以不唯一
|
196
|
-
- set 元素可以改变但唯一
|
197
|
-
|
198
|
-
变量类型
|
199
|
-
|
200
|
-
- 查看变量类型 type(x)
|
201
|
-
- 判断变量类型 isinstance(x, str)
|
202
|
-
|
203
|
-
函数: 不要判断类型, 否则会捕获不到异常, 无法定位到出问题的地方
|
204
|
-
|
205
|
-
函数: 必须有返回
|
206
|
-
|
207
|
-
- 有内容返回内容
|
208
|
-
- 无内容返回None
|
209
|
-
- 仅执行则返回True或False
|
210
|
-
|
211
|
-
函数变量
|
212
|
-
|
213
|
-
- 建议定义为 None
|
214
|
-
- 没有定义变量初始值, 添加 *args, **kwargs
|
215
|
-
- 定义了变量初始值, 添加 **kwargs
|
216
|
-
- 其它情况 *args, x=None, **kwargs
|
217
|
-
- 检查变量类型
|
218
|
-
|
219
|
-
配置文件用 JSON, 优点: 简单高效, 保持数据类型, 缺点: 不支持注释
|
220
|
-
|
221
|
-
```py
|
222
|
-
logger.info('配置文件解析...')
|
223
|
-
|
224
|
-
config_file = os.path.realpath(argument_config)
|
225
|
-
config_dict = utils.json_file_parser(config_file)
|
226
|
-
|
227
|
-
exit(1) if config_dict == None else next
|
228
|
-
|
229
|
-
print(config_dict)
|
230
|
-
```
|
231
|
-
|
232
|
-
## Exit
|
233
|
-
|
234
|
-
如果要编译脚本, 代码中有使用 exit 退出脚本时, 将 `exit()` 改为 `sys.exit()`, 否则编译的文件调用 `exit()` 会失败
|
ezkit-1.7.6/ezKit/__init__.py
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
# from . import bottle, database, files, plots, utils, weixin, zabbix
|
ezkit-1.7.6/ezKit/token.py
DELETED
@@ -1,44 +0,0 @@
|
|
1
|
-
import json
|
2
|
-
|
3
|
-
from .cipher import AESCipher
|
4
|
-
from .utils import datetime_now, datetime_offset, datetime_string_to_datetime, datetime_to_string, v_true
|
5
|
-
|
6
|
-
|
7
|
-
def generate_token(key: str = 'Fc0zXCmGKd7tPu6W', timeout: int = 3600, data: any = None) -> None | str:
|
8
|
-
try:
|
9
|
-
source = json.dumps(
|
10
|
-
obj={
|
11
|
-
'datetime': datetime_to_string(datetime_offset(datetime_now(), seconds=+timeout)),
|
12
|
-
'data': data
|
13
|
-
},
|
14
|
-
default=str
|
15
|
-
)
|
16
|
-
cipher = AESCipher(key=key, algorithm='sha256')
|
17
|
-
return cipher.encrypt(source)
|
18
|
-
except:
|
19
|
-
return None
|
20
|
-
|
21
|
-
|
22
|
-
def parsing_token(token_string: str, key: str = 'Fc0zXCmGKd7tPu6W') -> None | dict:
|
23
|
-
try:
|
24
|
-
if v_true(token_string, str):
|
25
|
-
cipher = AESCipher(key=key, algorithm='sha256')
|
26
|
-
source: dict = json.loads(cipher.decrypt(token_string))
|
27
|
-
source['datetime'] = datetime_string_to_datetime(source['datetime'])
|
28
|
-
return source
|
29
|
-
else:
|
30
|
-
return None
|
31
|
-
except:
|
32
|
-
return None
|
33
|
-
|
34
|
-
|
35
|
-
def certify_token(token_string: str, key: str = 'Fc0zXCmGKd7tPu6W') -> bool:
|
36
|
-
try:
|
37
|
-
result = parsing_token(token_string, key)
|
38
|
-
if not v_true(result, dict):
|
39
|
-
return False
|
40
|
-
if result.get('datetime') < datetime_now():
|
41
|
-
return False
|
42
|
-
return True
|
43
|
-
except:
|
44
|
-
return False
|
ezkit-1.7.6/ezKit/xftp.py
DELETED
@@ -1,194 +0,0 @@
|
|
1
|
-
'''
|
2
|
-
ftplib: https://docs.python.org/3.10/library/ftplib.html
|
3
|
-
'''
|
4
|
-
import os
|
5
|
-
from ftplib import FTP
|
6
|
-
from pathlib import Path
|
7
|
-
|
8
|
-
|
9
|
-
class XFTP:
|
10
|
-
|
11
|
-
def __init__(self, host='127.0.0.1', port=21, username='anonymous', password='', encoding='UTF-8', debuglevel=0):
|
12
|
-
''' Initiation '''
|
13
|
-
self.ftp = FTP()
|
14
|
-
self.ftp.set_debuglevel(debuglevel)
|
15
|
-
self.host = host
|
16
|
-
self.port = port
|
17
|
-
self.username = username
|
18
|
-
self.password = password
|
19
|
-
self.encoding = encoding
|
20
|
-
self.retry = 1
|
21
|
-
|
22
|
-
def connect(self):
|
23
|
-
''' FTP connect '''
|
24
|
-
try:
|
25
|
-
self.ftp.connect(host=self.host, port=self.port, timeout=10)
|
26
|
-
self.ftp.encoding = self.encoding
|
27
|
-
self.ftp.login(user=self.username, passwd=self.password)
|
28
|
-
print('FTP connect success')
|
29
|
-
print('-' * 80)
|
30
|
-
return True
|
31
|
-
except Exception as e:
|
32
|
-
print(f'FTP connect error: {e}, retry...')
|
33
|
-
if self.retry >= 3:
|
34
|
-
print('FTP connect faild')
|
35
|
-
return False
|
36
|
-
self.retry += 1
|
37
|
-
self.connect()
|
38
|
-
|
39
|
-
def close(self, info=None):
|
40
|
-
''' FTP close '''
|
41
|
-
print(info) if info else None
|
42
|
-
try:
|
43
|
-
self.ftp.quit()
|
44
|
-
except:
|
45
|
-
self.ftp.close()
|
46
|
-
print('-' * 80)
|
47
|
-
print('FTP connect closed')
|
48
|
-
|
49
|
-
def get_file_list(self, dir='/'):
|
50
|
-
''' Get file list '''
|
51
|
-
self.chdir_to_remote(dir)
|
52
|
-
return self.ftp.nlst()
|
53
|
-
|
54
|
-
def get_file_size(self, dir='/', file=None):
|
55
|
-
''' Get file size '''
|
56
|
-
self.chdir_to_remote(dir)
|
57
|
-
return self.ftp.size(file)
|
58
|
-
|
59
|
-
def mkdir(self, dir_string='/'):
|
60
|
-
''' 创建目录 (从 / 目录依次递增创建子目录. 如果目录存在, 创建目录时会报错, 所以这里忽略所有错误.) '''
|
61
|
-
try:
|
62
|
-
dir_list = dir_string.split("/")
|
63
|
-
for i, _ in enumerate(dir_list):
|
64
|
-
dir = '/'.join(dir_list[:i + 1])
|
65
|
-
try:
|
66
|
-
self.ftp.mkd(dir)
|
67
|
-
except:
|
68
|
-
pass
|
69
|
-
return True
|
70
|
-
except:
|
71
|
-
return False
|
72
|
-
|
73
|
-
def chdir_to_remote(self, dir='/'):
|
74
|
-
''' change to remote directory'''
|
75
|
-
try:
|
76
|
-
self.ftp.cwd(dir)
|
77
|
-
except:
|
78
|
-
self.close(f'Remote directory error: {dir}')
|
79
|
-
|
80
|
-
def x_exit(self, info=None):
|
81
|
-
''' Exit '''
|
82
|
-
print(info) if info else None
|
83
|
-
# 注意: exit() 并不会退出脚本, 配合 try 使用
|
84
|
-
exit()
|
85
|
-
|
86
|
-
def x_exec(self, local_dir='.', local_file='', remote_dir='/', remote_file='', upload=False):
|
87
|
-
''' Download or Upload '''
|
88
|
-
|
89
|
-
bufsize = 1024
|
90
|
-
local_path = local_dir + '/' + local_file
|
91
|
-
remote_path = remote_dir + '/' + remote_file
|
92
|
-
info = 'Upload' if upload else 'Download'
|
93
|
-
|
94
|
-
# 检查参数
|
95
|
-
if upload:
|
96
|
-
if local_file == '':
|
97
|
-
self.close('Argument Miss: local file')
|
98
|
-
# 如果没有设置 远程文件 名称, 则使用 本地文件 名称
|
99
|
-
if remote_file == '':
|
100
|
-
remote_file = local_file
|
101
|
-
remote_path = remote_dir + '/' + remote_file
|
102
|
-
else:
|
103
|
-
if remote_file == '':
|
104
|
-
self.close('Argument Miss: remote file')
|
105
|
-
# 如果没有设置 本地文件 名称, 则使用 远程文件 名称
|
106
|
-
if local_file == '':
|
107
|
-
local_file = remote_file
|
108
|
-
local_path = local_dir + '/' + local_file
|
109
|
-
|
110
|
-
# 进入本地目录
|
111
|
-
try:
|
112
|
-
if upload:
|
113
|
-
# 检查本地目录
|
114
|
-
stat = Path(local_dir)
|
115
|
-
self.close(f'Local directory error: {local_dir}') if stat.exists() == False else None
|
116
|
-
else:
|
117
|
-
# 创建本地目录
|
118
|
-
Path(local_dir).mkdir(parents=True, exist_ok=True)
|
119
|
-
# 进入本地目录
|
120
|
-
os.chdir(local_dir)
|
121
|
-
except:
|
122
|
-
# 第一层 try 使用 self.x_exit() 无效, 直接使用 self.close()
|
123
|
-
self.close(f'Local directory error: {local_dir}')
|
124
|
-
|
125
|
-
# 上传或下载
|
126
|
-
try:
|
127
|
-
|
128
|
-
if upload:
|
129
|
-
|
130
|
-
''' 上传 '''
|
131
|
-
|
132
|
-
# 创建远程目录
|
133
|
-
if remote_dir != '/':
|
134
|
-
self.mkdir(remote_dir)
|
135
|
-
|
136
|
-
# 进入远程目录
|
137
|
-
self.chdir_to_remote(remote_dir)
|
138
|
-
|
139
|
-
# 上传文件
|
140
|
-
stat = Path(local_file)
|
141
|
-
if stat.exists() and stat.is_file():
|
142
|
-
with open(local_file, 'rb') as fid:
|
143
|
-
self.ftp.storbinary(f'STOR {remote_file}', fid, bufsize)
|
144
|
-
print('{} success: {} -> {}'.format(info, local_path.replace('//', '/'), remote_path.replace('//', '/')))
|
145
|
-
return True
|
146
|
-
else:
|
147
|
-
self.x_exit('{} error: {} is not exist'.format(info, local_path.replace('//', '/')))
|
148
|
-
|
149
|
-
else:
|
150
|
-
|
151
|
-
''' 下载 '''
|
152
|
-
|
153
|
-
# 进入远程目录
|
154
|
-
self.chdir_to_remote(remote_dir)
|
155
|
-
|
156
|
-
# 下载文件
|
157
|
-
if remote_file in self.ftp.nlst():
|
158
|
-
with open(local_file, 'wb') as fid:
|
159
|
-
self.ftp.retrbinary(f'RETR {remote_file}', fid.write, bufsize)
|
160
|
-
print('{} success: {} -> {}'.format(info, remote_path.replace('//', '/'), local_path.replace('//', '/')))
|
161
|
-
return True
|
162
|
-
else:
|
163
|
-
self.x_exit('{} error: {} is not exist'.format(info, remote_path.replace('//', '/')))
|
164
|
-
|
165
|
-
except Exception as e:
|
166
|
-
# 第一层 try 使用 self.x_exit() 无效, 直接使用 self.close()
|
167
|
-
# self.close('{} faild! Please check {} or {}'.format(info, local_path, remote_path))
|
168
|
-
self.close(f'{info} error: {e}')
|
169
|
-
return False
|
170
|
-
|
171
|
-
def handle_all(self, local_dir='.', remote_dir='/', upload=False):
|
172
|
-
''' Handle All '''
|
173
|
-
if upload:
|
174
|
-
# 检查本地目录
|
175
|
-
stat = Path(local_dir)
|
176
|
-
self.close(f'Local directory error: {local_dir}') if stat.exists() == False else None
|
177
|
-
# 获取文件列表
|
178
|
-
local_files = [f for f in os.listdir(local_dir) if os.path.isfile(os.path.join(local_dir, f))]
|
179
|
-
for i in local_files:
|
180
|
-
self.x_exec(local_dir=local_dir, remote_dir=remote_dir, local_file=i, upload=True)
|
181
|
-
else:
|
182
|
-
remote_files = self.get_file_list(remote_dir)
|
183
|
-
for i in remote_files:
|
184
|
-
self.x_exec(local_dir=local_dir, remote_dir=remote_dir, remote_file=i)
|
185
|
-
|
186
|
-
def retrlines(self, remote_dir='/', cmd='LIST'):
|
187
|
-
''' Retrlines '''
|
188
|
-
try:
|
189
|
-
self.chdir_to_remote(remote_dir)
|
190
|
-
print(self.ftp.retrlines(cmd))
|
191
|
-
self.close()
|
192
|
-
except Exception as e:
|
193
|
-
# 第一层 try 使用 self.x_exit() 无效, 直接使用 self.close()
|
194
|
-
self.close(e)
|
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
|