mirror of
https://github.com/xxnuo/MTranServer.git
synced 2026-09-03 06:35:20 +08:00
refactor: prepare using js
feat: update remove python
This commit is contained in:
8
.gitignore
vendored
8
.gitignore
vendored
@@ -12,7 +12,11 @@ core
|
||||
/node_modules
|
||||
/packages
|
||||
|
||||
bin/worker
|
||||
bin/bin_hash.go
|
||||
/dist
|
||||
|
||||
/.claude
|
||||
|
||||
/tests/ff
|
||||
/tests/MTranCore
|
||||
deprecated/go/bin/worker
|
||||
deprecated/go/dist/mtranserver-darwin-arm64
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
3.12
|
||||
3
deprecated/go/README.md
Normal file
3
deprecated/go/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Golang version
|
||||
|
||||
The WASM runtime and glue code will panic in high concurrency, so it is temporarily abandoned, and the Node.js version is used.
|
||||
4
deprecated/go/bin/bin_hash.go
Normal file
4
deprecated/go/bin/bin_hash.go
Normal file
@@ -0,0 +1,4 @@
|
||||
// Code generated by go generate; DO NOT EDIT.
|
||||
package bin
|
||||
|
||||
const WorkerHash = "51d403e9477bad177fb74eaa4a936b40fb16697eb702e8a8c3b7157e896f829a"
|
||||
@@ -92,10 +92,6 @@ func TestManager_Logs(t *testing.T) {
|
||||
t.Logf("Collected %d log lines", len(logs))
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
func TestManager_Translate(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("Skipping integration test in short mode")
|
||||
@@ -195,7 +191,7 @@ func TestManager_NotStarted(t *testing.T) {
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
_, err := mgr.Ready(ctx)
|
||||
_, err := mgr.Health(ctx)
|
||||
assert.Error(t, err)
|
||||
|
||||
_, err = mgr.Translate(ctx, "Hello")
|
||||
@@ -229,19 +225,7 @@ func TestManager_FullWorkflow(t *testing.T) {
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
ready, err := mgr.Ready(ctx)
|
||||
require.NoError(t, err)
|
||||
assert.False(t, ready)
|
||||
|
||||
resp, err := mgr.Poweron(ctx, manager.PoweronRequest{
|
||||
Path: "path/to/model",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, resp)
|
||||
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
ready, err = mgr.Ready(ctx)
|
||||
ready, err := mgr.Health(ctx)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, ready)
|
||||
|
||||
@@ -255,17 +239,9 @@ func TestManager_FullWorkflow(t *testing.T) {
|
||||
assert.NotEmpty(t, htmlResult)
|
||||
t.Logf("HTML translation result: %s", htmlResult)
|
||||
|
||||
rebootResp, err := mgr.Reboot(ctx, manager.RebootRequest{
|
||||
Time: 0,
|
||||
Force: false,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, rebootResp)
|
||||
|
||||
poweroffResp, err := mgr.Poweroff(ctx, manager.PoweroffRequest{
|
||||
Time: 0,
|
||||
exitResp, err := mgr.Exit(ctx, manager.ExitRequest{
|
||||
Force: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, poweroffResp)
|
||||
assert.NotNil(t, exitResp)
|
||||
}
|
||||
@@ -1,3 +1,3 @@
|
||||
# Node.js version (deprecated)
|
||||
# Node.js v1 version
|
||||
|
||||
Node.js version is deprecated, continue to develop in [golang version](https://github.com/xxnuo/MTranServer)
|
||||
Node.js v1 version is deprecated
|
||||
@@ -1,9 +0,0 @@
|
||||
[project]
|
||||
name = "mtranserver"
|
||||
version = "3.1.14"
|
||||
description = "Add your description here"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = [
|
||||
"httpx>=0.28.1",
|
||||
]
|
||||
@@ -1,327 +0,0 @@
|
||||
"""
|
||||
MTranServer 测试脚本
|
||||
测试服务器的各个接口功能
|
||||
"""
|
||||
|
||||
import httpx
|
||||
import time
|
||||
from typing import Any
|
||||
|
||||
|
||||
class MTranServerTester:
|
||||
"""MTranServer 测试类"""
|
||||
|
||||
def __init__(self, base_url: str = "http://localhost:8989", api_token: str = ""):
|
||||
self.base_url = base_url
|
||||
self.api_token = api_token
|
||||
self.client = httpx.Client(timeout=30.0)
|
||||
self.headers = {}
|
||||
if api_token:
|
||||
self.headers["Authorization"] = f"Bearer {api_token}"
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
self.client.close()
|
||||
|
||||
def print_result(self, test_name: str, success: bool, message: str = "", data: Any = None):
|
||||
"""打印测试结果"""
|
||||
status = "✓" if success else "✗"
|
||||
print(f"\n[{status}] {test_name}")
|
||||
if message:
|
||||
print(f" {message}")
|
||||
if data:
|
||||
print(f" 数据: {data}")
|
||||
|
||||
def test_health(self) -> bool:
|
||||
"""测试健康检查接口"""
|
||||
try:
|
||||
response = self.client.get(f"{self.base_url}/health")
|
||||
success = response.status_code == 200
|
||||
self.print_result(
|
||||
"健康检查",
|
||||
success,
|
||||
f"状态码: {response.status_code}",
|
||||
response.json() if success else None
|
||||
)
|
||||
return success
|
||||
except Exception as e:
|
||||
self.print_result("健康检查", False, f"错误: {str(e)}")
|
||||
return False
|
||||
|
||||
def test_version(self) -> bool:
|
||||
"""测试版本接口"""
|
||||
try:
|
||||
response = self.client.get(f"{self.base_url}/version")
|
||||
success = response.status_code == 200
|
||||
self.print_result(
|
||||
"版本信息",
|
||||
success,
|
||||
f"状态码: {response.status_code}",
|
||||
response.json() if success else None
|
||||
)
|
||||
return success
|
||||
except Exception as e:
|
||||
self.print_result("版本信息", False, f"错误: {str(e)}")
|
||||
return False
|
||||
|
||||
def test_languages(self) -> bool:
|
||||
"""测试语言列表接口"""
|
||||
try:
|
||||
response = self.client.get(f"{self.base_url}/languages", headers=self.headers)
|
||||
success = response.status_code == 200
|
||||
data = response.json() if success else None
|
||||
self.print_result(
|
||||
"语言列表",
|
||||
success,
|
||||
f"状态码: {response.status_code}, 支持语言数: {len(data.get('languages', [])) if data else 0}",
|
||||
data
|
||||
)
|
||||
return success
|
||||
except Exception as e:
|
||||
self.print_result("语言列表", False, f"错误: {str(e)}")
|
||||
return False
|
||||
|
||||
def test_translate(self, text: str = "Hello, world!", from_lang: str = "en", to_lang: str = "zh-Hans") -> bool:
|
||||
"""测试单文本翻译接口"""
|
||||
try:
|
||||
start_time = time.time()
|
||||
response = self.client.post(
|
||||
f"{self.base_url}/translate",
|
||||
json={"from": from_lang, "to": to_lang, "text": text},
|
||||
headers=self.headers
|
||||
)
|
||||
elapsed = (time.time() - start_time) * 1000
|
||||
success = response.status_code == 200
|
||||
data = response.json() if success else None
|
||||
self.print_result(
|
||||
f"单文本翻译 ({from_lang} -> {to_lang})",
|
||||
success,
|
||||
f"状态码: {response.status_code}, 耗时: {elapsed:.2f}ms",
|
||||
{"原文": text, "译文": data.get("result") if data else None}
|
||||
)
|
||||
return success
|
||||
except Exception as e:
|
||||
self.print_result(f"单文本翻译 ({from_lang} -> {to_lang})", False, f"错误: {str(e)}")
|
||||
return False
|
||||
|
||||
def test_translate_batch(self, texts: list[str] = None, from_lang: str = "en", to_lang: str = "zh-Hans") -> bool:
|
||||
"""测试批量翻译接口"""
|
||||
if texts is None:
|
||||
texts = ["Hello, world!", "Good morning!", "How are you?"]
|
||||
|
||||
try:
|
||||
start_time = time.time()
|
||||
response = self.client.post(
|
||||
f"{self.base_url}/translate/batch",
|
||||
json={"from": from_lang, "to": to_lang, "texts": texts},
|
||||
headers=self.headers
|
||||
)
|
||||
elapsed = (time.time() - start_time) * 1000
|
||||
success = response.status_code == 200
|
||||
data = response.json() if success else None
|
||||
self.print_result(
|
||||
f"批量翻译 ({from_lang} -> {to_lang})",
|
||||
success,
|
||||
f"状态码: {response.status_code}, 文本数: {len(texts)}, 总耗时: {elapsed:.2f}ms, 平均: {elapsed/len(texts):.2f}ms",
|
||||
{"原文": texts, "译文": data.get("results") if data else None}
|
||||
)
|
||||
return success
|
||||
except Exception as e:
|
||||
self.print_result(f"批量翻译 ({from_lang} -> {to_lang})", False, f"错误: {str(e)}")
|
||||
return False
|
||||
|
||||
def test_google_compat(self, text: str = "The Great Pyramid of Giza", from_lang: str = "en", to_lang: str = "zh-Hans") -> bool:
|
||||
"""测试 Google 翻译兼容接口"""
|
||||
try:
|
||||
start_time = time.time()
|
||||
response = self.client.post(
|
||||
f"{self.base_url}/language/translate/v2",
|
||||
json={"q": text, "source": from_lang, "target": to_lang, "format": "text"},
|
||||
headers=self.headers
|
||||
)
|
||||
elapsed = (time.time() - start_time) * 1000
|
||||
success = response.status_code == 200
|
||||
data = response.json() if success else None
|
||||
result = data.get("data", {}).get("translations", [{}])[0].get("translatedText") if data else None
|
||||
self.print_result(
|
||||
f"Google 兼容接口 ({from_lang} -> {to_lang})",
|
||||
success,
|
||||
f"状态码: {response.status_code}, 耗时: {elapsed:.2f}ms",
|
||||
{"原文": text, "译文": result}
|
||||
)
|
||||
return success
|
||||
except Exception as e:
|
||||
self.print_result(f"Google 兼容接口 ({from_lang} -> {to_lang})", False, f"错误: {str(e)}")
|
||||
return False
|
||||
|
||||
def test_imme_plugin(self, texts: list[str] = None, from_lang: str = "en", to_lang: str = "zh-Hans") -> bool:
|
||||
"""测试沉浸式翻译插件接口"""
|
||||
if texts is None:
|
||||
texts = ["Hello, world!", "Good morning!"]
|
||||
|
||||
try:
|
||||
url = f"{self.base_url}/imme"
|
||||
if self.api_token:
|
||||
url += f"?token={self.api_token}"
|
||||
|
||||
start_time = time.time()
|
||||
response = self.client.post(
|
||||
url,
|
||||
json={"from": from_lang, "to": to_lang, "trans": texts}
|
||||
)
|
||||
elapsed = (time.time() - start_time) * 1000
|
||||
success = response.status_code == 200
|
||||
data = response.json() if success else None
|
||||
self.print_result(
|
||||
f"沉浸式翻译插件 ({from_lang} -> {to_lang})",
|
||||
success,
|
||||
f"状态码: {response.status_code}, 文本数: {len(texts)}, 耗时: {elapsed:.2f}ms",
|
||||
{"原文": texts, "译文": data.get("trans") if data else None}
|
||||
)
|
||||
return success
|
||||
except Exception as e:
|
||||
self.print_result(f"沉浸式翻译插件 ({from_lang} -> {to_lang})", False, f"错误: {str(e)}")
|
||||
return False
|
||||
|
||||
def test_kiss_plugin(self, text: str = "Hello, world!", from_lang: str = "en", to_lang: str = "zh-Hans") -> bool:
|
||||
"""测试简约翻译插件接口"""
|
||||
try:
|
||||
headers = {}
|
||||
if self.api_token:
|
||||
headers["KEY"] = self.api_token
|
||||
|
||||
start_time = time.time()
|
||||
response = self.client.post(
|
||||
f"{self.base_url}/kiss",
|
||||
json={"from": from_lang, "to": to_lang, "text": text},
|
||||
headers=headers
|
||||
)
|
||||
elapsed = (time.time() - start_time) * 1000
|
||||
success = response.status_code == 200
|
||||
data = response.json() if success else None
|
||||
self.print_result(
|
||||
f"简约翻译插件 ({from_lang} -> {to_lang})",
|
||||
success,
|
||||
f"状态码: {response.status_code}, 耗时: {elapsed:.2f}ms",
|
||||
{"原文": text, "译文": data.get("text") if data else None}
|
||||
)
|
||||
return success
|
||||
except Exception as e:
|
||||
self.print_result(f"简约翻译插件 ({from_lang} -> {to_lang})", False, f"错误: {str(e)}")
|
||||
return False
|
||||
|
||||
def test_performance(self, count: int = 10) -> bool:
|
||||
"""测试性能 - 连续翻译多次"""
|
||||
try:
|
||||
text = "Hello, world!"
|
||||
times = []
|
||||
|
||||
print(f"\n[性能测试] 连续翻译 {count} 次...")
|
||||
for i in range(count):
|
||||
start_time = time.time()
|
||||
response = self.client.post(
|
||||
f"{self.base_url}/translate",
|
||||
json={"from": "en", "to": "zh-Hans", "text": text},
|
||||
headers=self.headers
|
||||
)
|
||||
elapsed = (time.time() - start_time) * 1000
|
||||
times.append(elapsed)
|
||||
if response.status_code == 200:
|
||||
print(f" 第 {i+1} 次: {elapsed:.2f}ms")
|
||||
else:
|
||||
print(f" 第 {i+1} 次: 失败 (状态码: {response.status_code})")
|
||||
return False
|
||||
|
||||
avg_time = sum(times) / len(times)
|
||||
min_time = min(times)
|
||||
max_time = max(times)
|
||||
|
||||
self.print_result(
|
||||
"性能测试",
|
||||
True,
|
||||
f"平均: {avg_time:.2f}ms, 最快: {min_time:.2f}ms, 最慢: {max_time:.2f}ms"
|
||||
)
|
||||
return True
|
||||
except Exception as e:
|
||||
self.print_result("性能测试", False, f"错误: {str(e)}")
|
||||
return False
|
||||
|
||||
def run_all_tests(self):
|
||||
"""运行所有测试"""
|
||||
print("=" * 60)
|
||||
print("MTranServer 测试开始")
|
||||
print("=" * 60)
|
||||
print(f"服务器地址: {self.base_url}")
|
||||
print(f"API Token: {'已设置' if self.api_token else '未设置'}")
|
||||
|
||||
results = []
|
||||
|
||||
# 基础接口测试
|
||||
print("\n" + "=" * 60)
|
||||
print("基础接口测试")
|
||||
print("=" * 60)
|
||||
results.append(("健康检查", self.test_health()))
|
||||
results.append(("版本信息", self.test_version()))
|
||||
results.append(("语言列表", self.test_languages()))
|
||||
|
||||
# 翻译接口测试
|
||||
print("\n" + "=" * 60)
|
||||
print("翻译接口测试")
|
||||
print("=" * 60)
|
||||
results.append(("单文本翻译 (英->中)", self.test_translate("Hello, world!", "en", "zh-Hans")))
|
||||
results.append(("单文本翻译 (中->英)", self.test_translate("你好,世界!", "zh-Hans", "en")))
|
||||
results.append(("批量翻译", self.test_translate_batch()))
|
||||
results.append(("Google 兼容接口", self.test_google_compat()))
|
||||
|
||||
# 插件接口测试
|
||||
print("\n" + "=" * 60)
|
||||
print("插件接口测试")
|
||||
print("=" * 60)
|
||||
results.append(("沉浸式翻译插件", self.test_imme_plugin()))
|
||||
results.append(("简约翻译插件", self.test_kiss_plugin()))
|
||||
|
||||
# 性能测试
|
||||
print("\n" + "=" * 60)
|
||||
print("性能测试")
|
||||
print("=" * 60)
|
||||
results.append(("性能测试", self.test_performance(10)))
|
||||
|
||||
# 汇总结果
|
||||
print("\n" + "=" * 60)
|
||||
print("测试结果汇总")
|
||||
print("=" * 60)
|
||||
passed = sum(1 for _, success in results if success)
|
||||
total = len(results)
|
||||
print(f"\n通过: {passed}/{total}")
|
||||
|
||||
for name, success in results:
|
||||
status = "✓" if success else "✗"
|
||||
print(f" [{status}] {name}")
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
if passed == total:
|
||||
print("所有测试通过!")
|
||||
else:
|
||||
print(f"有 {total - passed} 个测试失败")
|
||||
print("=" * 60)
|
||||
|
||||
return passed == total
|
||||
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
import sys
|
||||
|
||||
# 从命令行参数获取配置
|
||||
base_url = sys.argv[1] if len(sys.argv) > 1 else "http://localhost:8989"
|
||||
api_token = sys.argv[2] if len(sys.argv) > 2 else ""
|
||||
|
||||
with MTranServerTester(base_url, api_token) as tester:
|
||||
success = tester.run_all_tests()
|
||||
sys.exit(0 if success else 1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
101
uv.lock
generated
101
uv.lock
generated
@@ -1,101 +0,0 @@
|
||||
version = 1
|
||||
revision = 3
|
||||
requires-python = ">=3.12"
|
||||
|
||||
[[package]]
|
||||
name = "anyio"
|
||||
version = "4.11.0"
|
||||
source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" }
|
||||
dependencies = [
|
||||
{ name = "idna" },
|
||||
{ name = "sniffio" },
|
||||
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
|
||||
]
|
||||
sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c6/78/7d432127c41b50bccba979505f272c16cbcadcc33645d5fa3a738110ae75/anyio-4.11.0.tar.gz", hash = "sha256:82a8d0b81e318cc5ce71a5f1f8b5c4e63619620b63141ef8c995fa0db95a57c4", size = 219094, upload-time = "2025-09-23T09:19:12.58Z" }
|
||||
wheels = [
|
||||
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl", hash = "sha256:0287e96f4d26d4149305414d4e3bc32f0dcd0862365a4bddea19d7a1ec38c4fc", size = 109097, upload-time = "2025-09-23T09:19:10.601Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "certifi"
|
||||
version = "2025.11.12"
|
||||
source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" }
|
||||
sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a2/8c/58f469717fa48465e4a50c014a0400602d3c437d7c0c468e17ada824da3a/certifi-2025.11.12.tar.gz", hash = "sha256:d8ab5478f2ecd78af242878415affce761ca6bc54a22a27e026d7c25357c3316", size = 160538, upload-time = "2025-11-12T02:54:51.517Z" }
|
||||
wheels = [
|
||||
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/70/7d/9bc192684cea499815ff478dfcdc13835ddf401365057044fb721ec6bddb/certifi-2025.11.12-py3-none-any.whl", hash = "sha256:97de8790030bbd5c2d96b7ec782fc2f7820ef8dba6db909ccf95449f2d062d4b", size = 159438, upload-time = "2025-11-12T02:54:49.735Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "h11"
|
||||
version = "0.16.0"
|
||||
source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" }
|
||||
sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" }
|
||||
wheels = [
|
||||
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httpcore"
|
||||
version = "1.0.9"
|
||||
source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" }
|
||||
dependencies = [
|
||||
{ name = "certifi" },
|
||||
{ name = "h11" },
|
||||
]
|
||||
sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" }
|
||||
wheels = [
|
||||
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httpx"
|
||||
version = "0.28.1"
|
||||
source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" }
|
||||
dependencies = [
|
||||
{ name = "anyio" },
|
||||
{ name = "certifi" },
|
||||
{ name = "httpcore" },
|
||||
{ name = "idna" },
|
||||
]
|
||||
sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" }
|
||||
wheels = [
|
||||
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "3.11"
|
||||
source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" }
|
||||
sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" }
|
||||
wheels = [
|
||||
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mtranserver"
|
||||
version = "0.1.0"
|
||||
source = { virtual = "." }
|
||||
dependencies = [
|
||||
{ name = "httpx" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [{ name = "httpx", specifier = ">=0.28.1" }]
|
||||
|
||||
[[package]]
|
||||
name = "sniffio"
|
||||
version = "1.3.1"
|
||||
source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" }
|
||||
sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" }
|
||||
wheels = [
|
||||
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.15.0"
|
||||
source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" }
|
||||
sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" }
|
||||
wheels = [
|
||||
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" },
|
||||
]
|
||||
Reference in New Issue
Block a user