pymecli 0.5.5__tar.gz → 0.5.6__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.
- {pymecli-0.5.5 → pymecli-0.5.6}/PKG-INFO +1 -1
- {pymecli-0.5.5 → pymecli-0.5.6}/cli/fast.py +7 -11
- {pymecli-0.5.5 → pymecli-0.5.6}/core/config.py +1 -4
- {pymecli-0.5.5 → pymecli-0.5.6}/core/redis_client.py +5 -14
- {pymecli-0.5.5 → pymecli-0.5.6}/pyproject.toml +1 -1
- {pymecli-0.5.5 → pymecli-0.5.6}/utils/mysql.py +56 -17
- pymecli-0.5.6/utils/pyredis.py +18 -0
- pymecli-0.5.5/utils/pyredis.py +0 -36
- {pymecli-0.5.5 → pymecli-0.5.6}/.gitignore +0 -0
- {pymecli-0.5.5 → pymecli-0.5.6}/README.md +0 -0
- {pymecli-0.5.5 → pymecli-0.5.6}/api/__init__.py +0 -0
- {pymecli-0.5.5 → pymecli-0.5.6}/api/v1/__init__.py +0 -0
- {pymecli-0.5.5 → pymecli-0.5.6}/api/v1/clash.py +0 -0
- {pymecli-0.5.5 → pymecli-0.5.6}/api/v1/redis.py +0 -0
- {pymecli-0.5.5 → pymecli-0.5.6}/cli/__init__.py +0 -0
- {pymecli-0.5.5 → pymecli-0.5.6}/cli/bitget.py +0 -0
- {pymecli-0.5.5 → pymecli-0.5.6}/cli/dst.py +0 -0
- {pymecli-0.5.5 → pymecli-0.5.6}/cli/example.py +0 -0
- {pymecli-0.5.5 → pymecli-0.5.6}/cli/gate.py +0 -0
- {pymecli-0.5.5 → pymecli-0.5.6}/cli/okx.py +0 -0
- {pymecli-0.5.5 → pymecli-0.5.6}/cli/redis_csv.py +0 -0
- {pymecli-0.5.5 → pymecli-0.5.6}/cli/tele.py +0 -0
- {pymecli-0.5.5 → pymecli-0.5.6}/cli/util.py +0 -0
- {pymecli-0.5.5 → pymecli-0.5.6}/core/__init__.py +0 -0
- {pymecli-0.5.5 → pymecli-0.5.6}/core/clash.py +0 -0
- {pymecli-0.5.5 → pymecli-0.5.6}/crypto/__init__.py +0 -0
- {pymecli-0.5.5 → pymecli-0.5.6}/crypto/bitget.py +0 -0
- {pymecli-0.5.5 → pymecli-0.5.6}/crypto/gate.py +0 -0
- {pymecli-0.5.5 → pymecli-0.5.6}/crypto/okx.py +0 -0
- {pymecli-0.5.5 → pymecli-0.5.6}/data/__init__.py +0 -0
- {pymecli-0.5.5 → pymecli-0.5.6}/data/dou_dict.py +0 -0
- {pymecli-0.5.5 → pymecli-0.5.6}/data/dou_list.py +0 -0
- {pymecli-0.5.5 → pymecli-0.5.6}/data/main.py +0 -0
- {pymecli-0.5.5 → pymecli-0.5.6}/data/template.yaml +0 -0
- {pymecli-0.5.5 → pymecli-0.5.6}/models/__init__.py +0 -0
- {pymecli-0.5.5 → pymecli-0.5.6}/models/douzero_model.py +0 -0
- {pymecli-0.5.5 → pymecli-0.5.6}/models/ocr_model.py +0 -0
- {pymecli-0.5.5 → pymecli-0.5.6}/models/response.py +0 -0
- {pymecli-0.5.5 → pymecli-0.5.6}/utils/__init__.py +0 -0
- {pymecli-0.5.5 → pymecli-0.5.6}/utils/elapsed.py +0 -0
- {pymecli-0.5.5 → pymecli-0.5.6}/utils/helper.py +0 -0
- {pymecli-0.5.5 → pymecli-0.5.6}/utils/logger.py +0 -0
- {pymecli-0.5.5 → pymecli-0.5.6}/utils/path.py +0 -0
- {pymecli-0.5.5 → pymecli-0.5.6}/utils/pd.py +0 -0
- {pymecli-0.5.5 → pymecli-0.5.6}/utils/sleep.py +0 -0
- {pymecli-0.5.5 → pymecli-0.5.6}/utils/text.py +0 -0
- {pymecli-0.5.5 → pymecli-0.5.6}/utils/toml.py +0 -0
|
@@ -18,11 +18,8 @@ from models.response import SuccessResponse
|
|
|
18
18
|
@asynccontextmanager
|
|
19
19
|
async def lifespan(app: FastAPI):
|
|
20
20
|
"""管理应用生命周期的上下文管理器"""
|
|
21
|
-
redis_pool = redis.ConnectionPool(
|
|
22
|
-
|
|
23
|
-
port=settings.REDIS_PORT,
|
|
24
|
-
db=settings.REDIS_DB,
|
|
25
|
-
password=settings.REDIS_PASSWORD,
|
|
21
|
+
redis_pool = redis.ConnectionPool.from_url(
|
|
22
|
+
settings.REDIS_URL,
|
|
26
23
|
max_connections=20, # 根据需要调整最大连接数
|
|
27
24
|
decode_responses=True,
|
|
28
25
|
)
|
|
@@ -152,16 +149,15 @@ def run_app(
|
|
|
152
149
|
"-p",
|
|
153
150
|
help="服务器代理,传入则通过代理转换Clash订阅,比如:socks5://127.0.0.1:7897",
|
|
154
151
|
),
|
|
155
|
-
|
|
152
|
+
redis_url: str = typer.Option(
|
|
156
153
|
None,
|
|
157
|
-
"--redis-
|
|
158
|
-
"
|
|
159
|
-
help="redis host",
|
|
154
|
+
"--redis-url",
|
|
155
|
+
help="redis url, 格式: redis://[user:password@]host[:port][/db]",
|
|
160
156
|
),
|
|
161
157
|
):
|
|
162
158
|
settings.reload()
|
|
163
|
-
if
|
|
164
|
-
settings.
|
|
159
|
+
if redis_url is not None:
|
|
160
|
+
settings.REDIS_URL = redis_url
|
|
165
161
|
|
|
166
162
|
clash_config = ClashConfig(rule, my_rule, proxy)
|
|
167
163
|
init_generator(clash_config)
|
|
@@ -13,10 +13,7 @@ class Settings(BaseSettings):
|
|
|
13
13
|
API_V1_STR: str = "/api/v1"
|
|
14
14
|
PROJECT_NAME: str = metadata["Name"]
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
REDIS_PORT: int = 6379
|
|
18
|
-
REDIS_DB: int = 0
|
|
19
|
-
REDIS_PASSWORD: str = ""
|
|
16
|
+
REDIS_URL: str = "redis://192.168.123.7:6379/0"
|
|
20
17
|
|
|
21
18
|
PROJECT_DESCRIPTION: str = (
|
|
22
19
|
f"{metadata['Summary']}, FastAPI提供: clash订阅转换、redis API"
|
|
@@ -8,26 +8,17 @@ import redis
|
|
|
8
8
|
class RedisClient:
|
|
9
9
|
def __init__(
|
|
10
10
|
self,
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
db=int(os.getenv("REDIS_DB", 0)),
|
|
14
|
-
password=os.getenv("REDIS_PASSWORD"),
|
|
15
|
-
decode_responses=True,
|
|
11
|
+
url: str = os.getenv("REDIS_URL", "redis://192.168.123.7:6379/0"),
|
|
12
|
+
decode_responses: bool = True,
|
|
16
13
|
):
|
|
17
14
|
"""
|
|
18
15
|
初始化Redis客户端
|
|
19
|
-
:param
|
|
20
|
-
:param port: Redis服务器端口
|
|
21
|
-
:param db: 数据库编号
|
|
22
|
-
:param password: 密码(如果需要)
|
|
16
|
+
:param url: Redis连接URL, 格式 redis://[user:password@]host[:port][/db]
|
|
23
17
|
:param decode_responses: 是否自动解码响应(将字节转换为字符串)
|
|
24
18
|
"""
|
|
25
19
|
|
|
26
|
-
self.client = redis.Redis(
|
|
27
|
-
|
|
28
|
-
port=port,
|
|
29
|
-
db=db,
|
|
30
|
-
password=password,
|
|
20
|
+
self.client = redis.Redis.from_url(
|
|
21
|
+
url,
|
|
31
22
|
decode_responses=decode_responses,
|
|
32
23
|
socket_connect_timeout=5,
|
|
33
24
|
socket_timeout=5,
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import os
|
|
2
|
+
import re
|
|
3
|
+
from urllib.parse import parse_qsl, urlencode, urlsplit, urlunsplit
|
|
2
4
|
|
|
3
5
|
import pandas as pd
|
|
4
6
|
from dotenv import load_dotenv
|
|
@@ -8,25 +10,62 @@ from utils import logger
|
|
|
8
10
|
from utils.pd import deduplicated, dt_to_timestamp
|
|
9
11
|
from utils.pyredis import get_redis_client
|
|
10
12
|
|
|
13
|
+
# Go go-sql-driver/mysql 专有参数,Python pymysql / SQLAlchemy 不支持
|
|
14
|
+
_GO_DSN_PARAMS = {
|
|
15
|
+
"parseTime", "loc", "timeout", "readTimeout", "writeTimeout",
|
|
16
|
+
"allowNativePasswords", "tls", "serverCert", "clientCert",
|
|
17
|
+
"clientKey", "multiStatements", "columnsWithAlias", "interpolateParams"}
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _strip_go_params(query: str) -> str:
|
|
21
|
+
"""从 query string 中移除 Go DSN 专有参数"""
|
|
22
|
+
if not query:
|
|
23
|
+
return query
|
|
24
|
+
kept = [(k, v) for k, v in parse_qsl(query) if k not in _GO_DSN_PARAMS]
|
|
25
|
+
return urlencode(kept)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _to_sqlalchemy_url(dsn: str) -> str:
|
|
29
|
+
"""
|
|
30
|
+
将 Go/Node 通用 MySQL DSN 转为 SQLAlchemy URL。
|
|
31
|
+
|
|
32
|
+
输入格式(Go go-sql-driver/mysql DSN 或 Node mysql URI):
|
|
33
|
+
root:password@192.168.123.7:3366/bot_tx?charset=utf8mb4&parseTime=True&loc=Local
|
|
34
|
+
root:password@tcp(192.168.123.7:3366)/bot_tx?charset=utf8mb4
|
|
35
|
+
mysql://root:password@192.168.123.7:3306/mydb
|
|
36
|
+
|
|
37
|
+
输出格式(SQLAlchemy):
|
|
38
|
+
mysql+pymysql://root:password@192.168.123.7:3366/bot_tx?charset=utf8mb4
|
|
39
|
+
"""
|
|
40
|
+
# 如果已经是完整的 scheme:// URL,直接处理
|
|
41
|
+
if "://" in dsn:
|
|
42
|
+
parts = urlsplit(dsn)
|
|
43
|
+
# mysql:// → mysql+pymysql://
|
|
44
|
+
scheme = parts.scheme
|
|
45
|
+
if scheme == "mysql":
|
|
46
|
+
scheme = "mysql+pymysql"
|
|
47
|
+
return urlunsplit((scheme, parts.netloc, parts.path, _strip_go_params(parts.query), ""))
|
|
48
|
+
|
|
49
|
+
# Go DSN 格式:[user[:password]@][tcp(]host[:port][)]/db[?params]
|
|
50
|
+
# 去掉 tcp() 包装
|
|
51
|
+
dsn = re.sub(r"tcp\(([^)]+)\)", r"\1", dsn)
|
|
52
|
+
|
|
53
|
+
# user:pass@host:port/db?query → mysql+pymysql://user:pass@host:port/db?query
|
|
54
|
+
url = f"mysql+pymysql://{dsn}"
|
|
55
|
+
parts = urlsplit(url)
|
|
56
|
+
return urlunsplit((parts.scheme, parts.netloc, parts.path, _strip_go_params(parts.query), ""))
|
|
57
|
+
|
|
11
58
|
|
|
12
59
|
def get_database_engine(env_path: str = ".env") -> Engine:
|
|
13
60
|
"""创建数据库引擎"""
|
|
14
61
|
load_dotenv(env_path)
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
user = os.getenv("MYSQL_USER")
|
|
19
|
-
password = os.getenv("MYSQL_PASSWORD")
|
|
20
|
-
|
|
21
|
-
engine = create_engine(
|
|
22
|
-
f"mysql+pymysql://{user}:{password}@{host}:{port}/{database}",
|
|
23
|
-
connect_args={
|
|
24
|
-
# 对于MySQL,可以在连接参数中指定时区
|
|
25
|
-
# "init_command": "SET time_zone='+00:00'", # UTC时间
|
|
26
|
-
# 或者使用本地时区,例如:
|
|
27
|
-
# "init_command": "SET time_zone='+08:00'", # 中国标准时间
|
|
28
|
-
},
|
|
62
|
+
dsn = os.getenv(
|
|
63
|
+
"MYSQL_URL",
|
|
64
|
+
"root:@127.0.0.1:3306/?charset=utf8mb4",
|
|
29
65
|
)
|
|
66
|
+
url = _to_sqlalchemy_url(dsn)
|
|
67
|
+
|
|
68
|
+
engine = create_engine(url, connect_args={})
|
|
30
69
|
|
|
31
70
|
try:
|
|
32
71
|
with engine.connect() as connection:
|
|
@@ -123,12 +162,12 @@ async def mysql_to_redis_and_csv(
|
|
|
123
162
|
id = f"{idx1}_{idx2}"
|
|
124
163
|
key = f"{key_prefix}:{id}"
|
|
125
164
|
|
|
126
|
-
#
|
|
165
|
+
# 转换行数据为字典(处理 NaN 为 None 或空字符串)
|
|
127
166
|
row_dict = row.where(pd.notna(row), "").to_dict()
|
|
128
167
|
|
|
129
|
-
# 1. 写入完整数据到 Hash
|
|
168
|
+
# 1. 写入完整数据到 Hash(自动覆盖)
|
|
130
169
|
pipe.hset(key, mapping=row_dict)
|
|
131
|
-
# 2. 写入 ZSet 索引:score =
|
|
170
|
+
# 2. 写入 ZSet 索引:score = 开仓时间戳(空值用 0 兜底)
|
|
132
171
|
score = row["open_at"]
|
|
133
172
|
if score is None or pd.isna(score):
|
|
134
173
|
score = 0
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
import redis as redis_sync
|
|
4
|
+
import redis.asyncio as redis
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def get_redis_client(
|
|
8
|
+
url: str = os.getenv("REDIS_URL", "redis://192.168.123.7:6379/0"),
|
|
9
|
+
decode_responses: bool = True,
|
|
10
|
+
) -> redis.Redis:
|
|
11
|
+
return redis.Redis.from_url(url, decode_responses=decode_responses)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def get_redis_client_sync(
|
|
15
|
+
url: str = os.getenv("REDIS_URL", "redis://192.168.123.7:6379/0"),
|
|
16
|
+
decode_responses: bool = True,
|
|
17
|
+
) -> redis_sync.Redis:
|
|
18
|
+
return redis_sync.Redis.from_url(url, decode_responses=decode_responses)
|
pymecli-0.5.5/utils/pyredis.py
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
|
|
3
|
-
import redis as redis_sync
|
|
4
|
-
import redis.asyncio as redis
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
def get_redis_client(
|
|
8
|
-
host=os.getenv("REDIS_HOST", "192.168.123.7"),
|
|
9
|
-
port=int(os.getenv("REDIS_PORT", 6379)),
|
|
10
|
-
db=int(os.getenv("REDIS_DB", 0)),
|
|
11
|
-
password=os.getenv("REDIS_PASSWORD"),
|
|
12
|
-
decode_responses=True,
|
|
13
|
-
) -> redis.Redis:
|
|
14
|
-
return redis.Redis(
|
|
15
|
-
host=host,
|
|
16
|
-
port=port,
|
|
17
|
-
db=db,
|
|
18
|
-
password=password,
|
|
19
|
-
decode_responses=decode_responses,
|
|
20
|
-
)
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
def get_redis_client_sync(
|
|
24
|
-
host=os.getenv("REDIS_HOST", "192.168.123.7"),
|
|
25
|
-
port=int(os.getenv("REDIS_PORT", 6379)),
|
|
26
|
-
db=int(os.getenv("REDIS_DB", 0)),
|
|
27
|
-
password=os.getenv("REDIS_PASSWORD"),
|
|
28
|
-
decode_responses=True,
|
|
29
|
-
) -> redis_sync.Redis:
|
|
30
|
-
return redis_sync.Redis(
|
|
31
|
-
host=host,
|
|
32
|
-
port=port,
|
|
33
|
-
db=db,
|
|
34
|
-
password=password,
|
|
35
|
-
decode_responses=decode_responses,
|
|
36
|
-
)
|
|
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
|
|
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
|