echotools 1.0.0__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.
- echotools-1.0.0/PKG-INFO +80 -0
- echotools-1.0.0/README.md +34 -0
- echotools-1.0.0/pyproject.toml +82 -0
- echotools-1.0.0/setup.cfg +4 -0
- echotools-1.0.0/src/echotools/__init__.py +149 -0
- echotools-1.0.0/src/echotools/cache/__init__.py +8 -0
- echotools-1.0.0/src/echotools/cache/list_cache.py +152 -0
- echotools-1.0.0/src/echotools/cache/memory_cache.py +72 -0
- echotools-1.0.0/src/echotools/config/__init__.py +23 -0
- echotools-1.0.0/src/echotools/config/base.py +144 -0
- echotools-1.0.0/src/echotools/config/center.py +443 -0
- echotools-1.0.0/src/echotools/config/loader.py +138 -0
- echotools-1.0.0/src/echotools/config/merge.py +29 -0
- echotools-1.0.0/src/echotools/dispatch/__init__.py +20 -0
- echotools-1.0.0/src/echotools/dispatch/candidate.py +51 -0
- echotools-1.0.0/src/echotools/dispatch/dispatcher.py +268 -0
- echotools-1.0.0/src/echotools/dispatch/selector.py +390 -0
- echotools-1.0.0/src/echotools/errors/__init__.py +29 -0
- echotools-1.0.0/src/echotools/errors/base.py +32 -0
- echotools-1.0.0/src/echotools/errors/classify.py +42 -0
- echotools-1.0.0/src/echotools/errors/common.py +74 -0
- echotools-1.0.0/src/echotools/events/__init__.py +8 -0
- echotools-1.0.0/src/echotools/events/bus.py +104 -0
- echotools-1.0.0/src/echotools/events/event.py +31 -0
- echotools-1.0.0/src/echotools/files/__init__.py +7 -0
- echotools-1.0.0/src/echotools/files/file_util.py +155 -0
- echotools-1.0.0/src/echotools/fncall/__init__.py +40 -0
- echotools-1.0.0/src/echotools/fncall/parsers/__init__.py +6 -0
- echotools-1.0.0/src/echotools/fncall/parsers/stream.py +156 -0
- echotools-1.0.0/src/echotools/fncall/parsers/xml_parser.py +320 -0
- echotools-1.0.0/src/echotools/fncall/prompt/__init__.py +5 -0
- echotools-1.0.0/src/echotools/fncall/prompt/history.py +383 -0
- echotools-1.0.0/src/echotools/fncall/prompt/inject.py +108 -0
- echotools-1.0.0/src/echotools/fncall/prompt/templates.py +159 -0
- echotools-1.0.0/src/echotools/fncall/protocols/__init__.py +28 -0
- echotools-1.0.0/src/echotools/fncall/protocols/antml.py +251 -0
- echotools-1.0.0/src/echotools/fncall/protocols/bracket.py +154 -0
- echotools-1.0.0/src/echotools/fncall/protocols/custom.py +50 -0
- echotools-1.0.0/src/echotools/fncall/protocols/nous.py +161 -0
- echotools-1.0.0/src/echotools/fncall/protocols/original.py +378 -0
- echotools-1.0.0/src/echotools/fncall/protocols/xml.py +249 -0
- echotools-1.0.0/src/echotools/fncall/registry.py +96 -0
- echotools-1.0.0/src/echotools/fncall/shared/__init__.py +25 -0
- echotools-1.0.0/src/echotools/fncall/shared/coercion.py +295 -0
- echotools-1.0.0/src/echotools/fncall/shared/loop_detect.py +97 -0
- echotools-1.0.0/src/echotools/fncall/shared/normalization.py +170 -0
- echotools-1.0.0/src/echotools/fncall/shared/xml_helpers.py +56 -0
- echotools-1.0.0/src/echotools/ids/__init__.py +7 -0
- echotools-1.0.0/src/echotools/ids/generator.py +62 -0
- echotools-1.0.0/src/echotools/io/__init__.py +11 -0
- echotools-1.0.0/src/echotools/io/io_utils.py +81 -0
- echotools-1.0.0/src/echotools/lifecycle/__init__.py +8 -0
- echotools-1.0.0/src/echotools/lifecycle/manager.py +72 -0
- echotools-1.0.0/src/echotools/lifecycle/updater.py +176 -0
- echotools-1.0.0/src/echotools/logger/__init__.py +12 -0
- echotools-1.0.0/src/echotools/logger/manager.py +164 -0
- echotools-1.0.0/src/echotools/network/__init__.py +11 -0
- echotools-1.0.0/src/echotools/network/http_utils.py +68 -0
- echotools-1.0.0/src/echotools/plugin/__init__.py +9 -0
- echotools-1.0.0/src/echotools/plugin/base.py +41 -0
- echotools-1.0.0/src/echotools/plugin/discovery.py +134 -0
- echotools-1.0.0/src/echotools/plugin/registry.py +253 -0
- echotools-1.0.0/src/echotools/process/__init__.py +10 -0
- echotools-1.0.0/src/echotools/process/port.py +183 -0
- echotools-1.0.0/src/echotools/protocol/__init__.py +19 -0
- echotools-1.0.0/src/echotools/protocol/base.py +123 -0
- echotools-1.0.0/src/echotools/proxy/__init__.py +7 -0
- echotools-1.0.0/src/echotools/proxy/manager.py +269 -0
- echotools-1.0.0/src/echotools/retry/__init__.py +11 -0
- echotools-1.0.0/src/echotools/retry/retry.py +127 -0
- echotools-1.0.0/src/echotools/runtime/__init__.py +7 -0
- echotools-1.0.0/src/echotools/runtime/collector.py +60 -0
- echotools-1.0.0/src/echotools/scheduler/__init__.py +7 -0
- echotools-1.0.0/src/echotools/scheduler/scheduler.py +69 -0
- echotools-1.0.0/src/echotools/sdk/__init__.py +7 -0
- echotools-1.0.0/src/echotools/sdk/facade.py +154 -0
- echotools-1.0.0/src/echotools/tracing/__init__.py +28 -0
- echotools-1.0.0/src/echotools/tracing/context.py +63 -0
- echotools-1.0.0/src/echotools/tracing/span.py +135 -0
- echotools-1.0.0/src/echotools/tracing/tracer.py +97 -0
- echotools-1.0.0/src/echotools/watcher/__init__.py +7 -0
- echotools-1.0.0/src/echotools/watcher/file_watcher.py +110 -0
- echotools-1.0.0/src/echotools/web/__init__.py +8 -0
- echotools-1.0.0/src/echotools/web/application.py +120 -0
- echotools-1.0.0/src/echotools/web/utils.py +186 -0
- echotools-1.0.0/src/echotools.egg-info/PKG-INFO +80 -0
- echotools-1.0.0/src/echotools.egg-info/SOURCES.txt +95 -0
- echotools-1.0.0/src/echotools.egg-info/dependency_links.txt +1 -0
- echotools-1.0.0/src/echotools.egg-info/requires.txt +33 -0
- echotools-1.0.0/src/echotools.egg-info/top_level.txt +1 -0
- echotools-1.0.0/tests/test_cache_retry.py +33 -0
- echotools-1.0.0/tests/test_config.py +36 -0
- echotools-1.0.0/tests/test_dispatch.py +35 -0
- echotools-1.0.0/tests/test_events.py +35 -0
- echotools-1.0.0/tests/test_plugin.py +31 -0
- echotools-1.0.0/tests/test_protocols.py +22 -0
- echotools-1.0.0/tests/test_tracing.py +30 -0
echotools-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: echotools
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: 通用基础设施 SDK:配置、日志、事件、调度、插件、协议、调用链
|
|
5
|
+
Author: nichengfuben
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/nichengfuben/echotools
|
|
8
|
+
Project-URL: Repository, https://github.com/nichengfuben/echotools
|
|
9
|
+
Project-URL: Issues, https://github.com/nichengfuben/echotools/issues
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
21
|
+
Requires-Python: >=3.8
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
Requires-Dist: typing-extensions>=4.7.0
|
|
24
|
+
Provides-Extra: toml
|
|
25
|
+
Requires-Dist: tomlkit>=0.11.0; extra == "toml"
|
|
26
|
+
Requires-Dist: tomli>=2.0.0; python_version < "3.11" and extra == "toml"
|
|
27
|
+
Provides-Extra: watch
|
|
28
|
+
Requires-Dist: watchdog>=3.0.0; extra == "watch"
|
|
29
|
+
Provides-Extra: http
|
|
30
|
+
Requires-Dist: aiohttp>=3.8.0; extra == "http"
|
|
31
|
+
Provides-Extra: socks
|
|
32
|
+
Requires-Dist: aiohttp-socks>=0.8.0; extra == "socks"
|
|
33
|
+
Provides-Extra: all
|
|
34
|
+
Requires-Dist: tomlkit>=0.11.0; extra == "all"
|
|
35
|
+
Requires-Dist: tomli>=2.0.0; python_version < "3.11" and extra == "all"
|
|
36
|
+
Requires-Dist: watchdog>=3.0.0; extra == "all"
|
|
37
|
+
Requires-Dist: aiohttp>=3.8.0; extra == "all"
|
|
38
|
+
Requires-Dist: aiohttp-socks>=0.8.0; extra == "all"
|
|
39
|
+
Provides-Extra: dev
|
|
40
|
+
Requires-Dist: pytest>=7.4.0; extra == "dev"
|
|
41
|
+
Requires-Dist: pytest-asyncio>=0.23.0; extra == "dev"
|
|
42
|
+
Requires-Dist: pytest-cov>=4.1.0; extra == "dev"
|
|
43
|
+
Requires-Dist: mypy>=1.5.0; extra == "dev"
|
|
44
|
+
Requires-Dist: black>=23.0.0; extra == "dev"
|
|
45
|
+
Requires-Dist: isort>=5.12.0; extra == "dev"
|
|
46
|
+
|
|
47
|
+
# echotools
|
|
48
|
+
|
|
49
|
+
通用基础设施 SDK:配置中心、日志、事件总线、调用链、任务调度、
|
|
50
|
+
插件框架、协议系统、自适应选择器,完全项目无关,兼容 Python 3.8-3.14。
|
|
51
|
+
|
|
52
|
+
## 安装
|
|
53
|
+
|
|
54
|
+
pip install echotools[all]
|
|
55
|
+
|
|
56
|
+
## 快速开始
|
|
57
|
+
|
|
58
|
+
from echotools import EchoTools
|
|
59
|
+
|
|
60
|
+
et = EchoTools(service_name="myapp")
|
|
61
|
+
et.logger.configure(level="INFO", color=True)
|
|
62
|
+
cfg = et.config
|
|
63
|
+
cfg.load("config.toml")
|
|
64
|
+
|
|
65
|
+
with et.tracer.trace("request") as trace:
|
|
66
|
+
with et.tracer.span(trace, "db") as span:
|
|
67
|
+
span.set_tag("query", "select 1")
|
|
68
|
+
|
|
69
|
+
## 能力总览
|
|
70
|
+
|
|
71
|
+
- ConfigCenter 点路径配置 + 热重载 + 类型绑定
|
|
72
|
+
- LoggerManager 调用链注入 + 颜色 + 轮转
|
|
73
|
+
- EventBus 同步/异步事件
|
|
74
|
+
- Tracer 轻量调用链
|
|
75
|
+
- TaskDispatcher 单发/竞速 + 自适应选择
|
|
76
|
+
- PluginRegistry 自动发现 + 热重载
|
|
77
|
+
- get_protocol XML/antml/original/bracket/nous/custom 协议
|
|
78
|
+
- ProxyManager HTTP/HTTPS/SOCKS
|
|
79
|
+
- AutoUpdater git 自动更新
|
|
80
|
+
- FileWatcher 轮询文件监视
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# echotools
|
|
2
|
+
|
|
3
|
+
通用基础设施 SDK:配置中心、日志、事件总线、调用链、任务调度、
|
|
4
|
+
插件框架、协议系统、自适应选择器,完全项目无关,兼容 Python 3.8-3.14。
|
|
5
|
+
|
|
6
|
+
## 安装
|
|
7
|
+
|
|
8
|
+
pip install echotools[all]
|
|
9
|
+
|
|
10
|
+
## 快速开始
|
|
11
|
+
|
|
12
|
+
from echotools import EchoTools
|
|
13
|
+
|
|
14
|
+
et = EchoTools(service_name="myapp")
|
|
15
|
+
et.logger.configure(level="INFO", color=True)
|
|
16
|
+
cfg = et.config
|
|
17
|
+
cfg.load("config.toml")
|
|
18
|
+
|
|
19
|
+
with et.tracer.trace("request") as trace:
|
|
20
|
+
with et.tracer.span(trace, "db") as span:
|
|
21
|
+
span.set_tag("query", "select 1")
|
|
22
|
+
|
|
23
|
+
## 能力总览
|
|
24
|
+
|
|
25
|
+
- ConfigCenter 点路径配置 + 热重载 + 类型绑定
|
|
26
|
+
- LoggerManager 调用链注入 + 颜色 + 轮转
|
|
27
|
+
- EventBus 同步/异步事件
|
|
28
|
+
- Tracer 轻量调用链
|
|
29
|
+
- TaskDispatcher 单发/竞速 + 自适应选择
|
|
30
|
+
- PluginRegistry 自动发现 + 热重载
|
|
31
|
+
- get_protocol XML/antml/original/bracket/nous/custom 协议
|
|
32
|
+
- ProxyManager HTTP/HTTPS/SOCKS
|
|
33
|
+
- AutoUpdater git 自动更新
|
|
34
|
+
- FileWatcher 轮询文件监视
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "echotools"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "通用基础设施 SDK:配置、日志、事件、调度、插件、协议、调用链"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.8"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "nichengfuben" }]
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Development Status :: 4 - Beta",
|
|
15
|
+
"Intended Audience :: Developers",
|
|
16
|
+
"License :: OSI Approved :: MIT License",
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"Programming Language :: Python :: 3.8",
|
|
19
|
+
"Programming Language :: Python :: 3.9",
|
|
20
|
+
"Programming Language :: Python :: 3.10",
|
|
21
|
+
"Programming Language :: Python :: 3.11",
|
|
22
|
+
"Programming Language :: Python :: 3.12",
|
|
23
|
+
"Programming Language :: Python :: 3.13",
|
|
24
|
+
"Topic :: Software Development :: Libraries",
|
|
25
|
+
]
|
|
26
|
+
dependencies = [
|
|
27
|
+
"typing-extensions>=4.7.0",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
[project.optional-dependencies]
|
|
31
|
+
toml = ["tomlkit>=0.11.0", "tomli>=2.0.0; python_version < '3.11'"]
|
|
32
|
+
watch = ["watchdog>=3.0.0"]
|
|
33
|
+
http = ["aiohttp>=3.8.0"]
|
|
34
|
+
socks = ["aiohttp-socks>=0.8.0"]
|
|
35
|
+
all = [
|
|
36
|
+
"tomlkit>=0.11.0",
|
|
37
|
+
"tomli>=2.0.0; python_version < '3.11'",
|
|
38
|
+
"watchdog>=3.0.0",
|
|
39
|
+
"aiohttp>=3.8.0",
|
|
40
|
+
"aiohttp-socks>=0.8.0",
|
|
41
|
+
]
|
|
42
|
+
dev = [
|
|
43
|
+
"pytest>=7.4.0",
|
|
44
|
+
"pytest-asyncio>=0.23.0",
|
|
45
|
+
"pytest-cov>=4.1.0",
|
|
46
|
+
"mypy>=1.5.0",
|
|
47
|
+
"black>=23.0.0",
|
|
48
|
+
"isort>=5.12.0",
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
[project.urls]
|
|
52
|
+
Homepage = "https://github.com/nichengfuben/echotools"
|
|
53
|
+
Repository = "https://github.com/nichengfuben/echotools"
|
|
54
|
+
Issues = "https://github.com/nichengfuben/echotools/issues"
|
|
55
|
+
|
|
56
|
+
[tool.black]
|
|
57
|
+
line-length = 88
|
|
58
|
+
target-version = ["py38", "py39", "py310", "py311", "py312", "py313"]
|
|
59
|
+
|
|
60
|
+
[tool.isort]
|
|
61
|
+
profile = "black"
|
|
62
|
+
line_length = 88
|
|
63
|
+
known_first_party = ["echotools"]
|
|
64
|
+
|
|
65
|
+
[tool.pytest.ini_options]
|
|
66
|
+
testpaths = ["tests"]
|
|
67
|
+
asyncio_mode = "auto"
|
|
68
|
+
addopts = ["--strict-markers", "--tb=short"]
|
|
69
|
+
|
|
70
|
+
[tool.coverage.run]
|
|
71
|
+
source = ["src/echotools"]
|
|
72
|
+
branch = true
|
|
73
|
+
omit = ["*/tests/*", "*/__init__.py"]
|
|
74
|
+
|
|
75
|
+
[tool.coverage.report]
|
|
76
|
+
fail_under = 90
|
|
77
|
+
exclude_lines = [
|
|
78
|
+
"pragma: no cover",
|
|
79
|
+
"def __repr__",
|
|
80
|
+
"if TYPE_CHECKING:",
|
|
81
|
+
"raise NotImplementedError",
|
|
82
|
+
]
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
"""echotools:通用基础设施 SDK。
|
|
4
|
+
|
|
5
|
+
完全项目无关,支持调用链,覆盖 Python 3.8-3.14。
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from echotools.cache import ListCache, MemoryCache
|
|
9
|
+
from echotools.config import ConfigBase, ConfigCenter
|
|
10
|
+
from echotools.dispatch import (
|
|
11
|
+
AdaptiveSelector,
|
|
12
|
+
TaskCandidate,
|
|
13
|
+
TaskDispatcher,
|
|
14
|
+
make_id,
|
|
15
|
+
)
|
|
16
|
+
from echotools.errors import (
|
|
17
|
+
ConfigError,
|
|
18
|
+
EchoError,
|
|
19
|
+
NetworkError,
|
|
20
|
+
NoCandidateError,
|
|
21
|
+
NotSupportedError,
|
|
22
|
+
PluginError,
|
|
23
|
+
ProtocolError,
|
|
24
|
+
TimeoutError,
|
|
25
|
+
ValidationError,
|
|
26
|
+
classify_http_error,
|
|
27
|
+
)
|
|
28
|
+
from echotools.events import Event, EventBus
|
|
29
|
+
from echotools.files import FileUtil
|
|
30
|
+
from echotools.ids import short_id, span_id, trace_id, uuid7
|
|
31
|
+
from echotools.io import (
|
|
32
|
+
atomic_write_text,
|
|
33
|
+
ensure_directory,
|
|
34
|
+
read_text_if_exists,
|
|
35
|
+
)
|
|
36
|
+
from echotools.lifecycle import AutoUpdater, LifecycleManager
|
|
37
|
+
from echotools.logger import LoggerManager, configure, get_logger, set_color
|
|
38
|
+
from echotools.plugin import Plugin, PluginRegistry, discover_plugins
|
|
39
|
+
from echotools.process import PortReleaseResult, ensure_port_available
|
|
40
|
+
from echotools.protocol.base import (
|
|
41
|
+
ToolProtocol,
|
|
42
|
+
get_protocol_by_id,
|
|
43
|
+
list_protocols,
|
|
44
|
+
register_protocol,
|
|
45
|
+
)
|
|
46
|
+
from echotools.proxy import ProxyManager
|
|
47
|
+
from echotools.retry import (
|
|
48
|
+
retry_on_empty,
|
|
49
|
+
retry_on_exception,
|
|
50
|
+
retry_with_backoff,
|
|
51
|
+
)
|
|
52
|
+
from echotools.runtime import RuntimeCollector
|
|
53
|
+
from echotools.scheduler import TaskScheduler
|
|
54
|
+
from echotools.sdk import EchoTools
|
|
55
|
+
from echotools.tracing import (
|
|
56
|
+
Span,
|
|
57
|
+
Trace,
|
|
58
|
+
Tracer,
|
|
59
|
+
get_current_span_id,
|
|
60
|
+
get_current_trace_id,
|
|
61
|
+
get_request_id,
|
|
62
|
+
set_current_span_id,
|
|
63
|
+
set_current_trace_id,
|
|
64
|
+
set_request_id,
|
|
65
|
+
)
|
|
66
|
+
from echotools.web import WebApplication, json_body, safe_flush
|
|
67
|
+
|
|
68
|
+
__version__ = "1.0.0"
|
|
69
|
+
|
|
70
|
+
__all__ = [
|
|
71
|
+
"__version__",
|
|
72
|
+
# SDK 门面
|
|
73
|
+
"EchoTools",
|
|
74
|
+
# 配置
|
|
75
|
+
"ConfigBase",
|
|
76
|
+
"ConfigCenter",
|
|
77
|
+
# 日志
|
|
78
|
+
"LoggerManager",
|
|
79
|
+
"get_logger",
|
|
80
|
+
"set_color",
|
|
81
|
+
"configure",
|
|
82
|
+
# 事件
|
|
83
|
+
"Event",
|
|
84
|
+
"EventBus",
|
|
85
|
+
# 调用链
|
|
86
|
+
"Tracer",
|
|
87
|
+
"Trace",
|
|
88
|
+
"Span",
|
|
89
|
+
"get_current_trace_id",
|
|
90
|
+
"set_current_trace_id",
|
|
91
|
+
"get_current_span_id",
|
|
92
|
+
"set_current_span_id",
|
|
93
|
+
"get_request_id",
|
|
94
|
+
"set_request_id",
|
|
95
|
+
# 缓存
|
|
96
|
+
"ListCache",
|
|
97
|
+
"MemoryCache",
|
|
98
|
+
# 调度
|
|
99
|
+
"TaskCandidate",
|
|
100
|
+
"make_id",
|
|
101
|
+
"AdaptiveSelector",
|
|
102
|
+
"TaskDispatcher",
|
|
103
|
+
"TaskScheduler",
|
|
104
|
+
# 插件
|
|
105
|
+
"Plugin",
|
|
106
|
+
"PluginRegistry",
|
|
107
|
+
"discover_plugins",
|
|
108
|
+
# 协议
|
|
109
|
+
"ToolProtocol",
|
|
110
|
+
"register_protocol",
|
|
111
|
+
"get_protocol_by_id",
|
|
112
|
+
"list_protocols",
|
|
113
|
+
# 生命周期
|
|
114
|
+
"LifecycleManager",
|
|
115
|
+
"AutoUpdater",
|
|
116
|
+
# 网络/代理/进程
|
|
117
|
+
"ProxyManager",
|
|
118
|
+
"PortReleaseResult",
|
|
119
|
+
"ensure_port_available",
|
|
120
|
+
# 运行时
|
|
121
|
+
"RuntimeCollector",
|
|
122
|
+
# Web
|
|
123
|
+
"WebApplication",
|
|
124
|
+
"json_body",
|
|
125
|
+
"safe_flush",
|
|
126
|
+
# 工具
|
|
127
|
+
"FileUtil",
|
|
128
|
+
"uuid7",
|
|
129
|
+
"short_id",
|
|
130
|
+
"trace_id",
|
|
131
|
+
"span_id",
|
|
132
|
+
"atomic_write_text",
|
|
133
|
+
"ensure_directory",
|
|
134
|
+
"read_text_if_exists",
|
|
135
|
+
"retry_with_backoff",
|
|
136
|
+
"retry_on_empty",
|
|
137
|
+
"retry_on_exception",
|
|
138
|
+
# 错误
|
|
139
|
+
"EchoError",
|
|
140
|
+
"ConfigError",
|
|
141
|
+
"ValidationError",
|
|
142
|
+
"NetworkError",
|
|
143
|
+
"TimeoutError",
|
|
144
|
+
"NotSupportedError",
|
|
145
|
+
"NoCandidateError",
|
|
146
|
+
"PluginError",
|
|
147
|
+
"ProtocolError",
|
|
148
|
+
"classify_http_error",
|
|
149
|
+
]
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
"""通用列表缓存:持久化 + 定时刷新 + 合并策略。
|
|
4
|
+
|
|
5
|
+
从 ModelsCache 抽象为完全通用的版本,不预设"模型"语义。
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import asyncio
|
|
9
|
+
import json
|
|
10
|
+
import time
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Awaitable, Callable, List, Optional
|
|
13
|
+
|
|
14
|
+
from echotools.logger.manager import get_logger
|
|
15
|
+
|
|
16
|
+
__all__ = ["ListCache"]
|
|
17
|
+
|
|
18
|
+
logger = get_logger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ListCache:
|
|
22
|
+
"""通用字符串列表缓存管理器。
|
|
23
|
+
|
|
24
|
+
职责:
|
|
25
|
+
1. 从持久化文件读取缓存
|
|
26
|
+
2. 定时调用 fetch_fn 刷新远程列表
|
|
27
|
+
3. 根据 overwrite 决定覆盖或追加
|
|
28
|
+
4. 更新后触发 on_update 回调
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def __init__(
|
|
32
|
+
self,
|
|
33
|
+
name: str,
|
|
34
|
+
fallback: List[str],
|
|
35
|
+
cache_path: str,
|
|
36
|
+
overwrite: bool = True,
|
|
37
|
+
) -> None:
|
|
38
|
+
"""初始化列表缓存。
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
name: 缓存标识名(仅用于日志)。
|
|
42
|
+
fallback: 兜底列表。
|
|
43
|
+
cache_path: 持久化文件路径。
|
|
44
|
+
overwrite: True=覆盖,False=只增不减。
|
|
45
|
+
"""
|
|
46
|
+
self._name = name
|
|
47
|
+
self._fallback = list(fallback)
|
|
48
|
+
self._overwrite = overwrite
|
|
49
|
+
self._items: List[str] = list(fallback)
|
|
50
|
+
self._cache_path = Path(cache_path)
|
|
51
|
+
self._refreshing = False
|
|
52
|
+
|
|
53
|
+
async def load(self) -> List[str]:
|
|
54
|
+
"""从缓存文件加载列表。
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
缓存列表,无缓存则返回兜底列表。
|
|
58
|
+
"""
|
|
59
|
+
try:
|
|
60
|
+
if self._cache_path.is_file():
|
|
61
|
+
text = self._cache_path.read_text(encoding="utf-8")
|
|
62
|
+
data = json.loads(text)
|
|
63
|
+
items = data.get("items", [])
|
|
64
|
+
if items:
|
|
65
|
+
self._items = list(items)
|
|
66
|
+
logger.info(
|
|
67
|
+
"[%s] 从缓存加载 %d 项",
|
|
68
|
+
self._name,
|
|
69
|
+
len(self._items),
|
|
70
|
+
)
|
|
71
|
+
except Exception as e:
|
|
72
|
+
logger.warning("[%s] 缓存加载失败: %s", self._name, e)
|
|
73
|
+
return list(self._items)
|
|
74
|
+
|
|
75
|
+
async def save(self, items: List[str]) -> None:
|
|
76
|
+
"""保存列表到缓存文件。
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
items: 要保存的列表。
|
|
80
|
+
"""
|
|
81
|
+
try:
|
|
82
|
+
self._cache_path.parent.mkdir(parents=True, exist_ok=True)
|
|
83
|
+
data = {"items": items, "updated_at": int(time.time())}
|
|
84
|
+
self._cache_path.write_text(
|
|
85
|
+
json.dumps(data, ensure_ascii=False, indent=2),
|
|
86
|
+
encoding="utf-8",
|
|
87
|
+
)
|
|
88
|
+
except Exception as e:
|
|
89
|
+
logger.warning("[%s] 缓存保存失败: %s", self._name, e)
|
|
90
|
+
|
|
91
|
+
def _merge(self, remote: List[str]) -> List[str]:
|
|
92
|
+
"""根据策略合并列表。"""
|
|
93
|
+
if self._overwrite:
|
|
94
|
+
return list(remote) if remote else list(self._items)
|
|
95
|
+
existing = set(self._items)
|
|
96
|
+
merged = list(self._items)
|
|
97
|
+
for m in remote:
|
|
98
|
+
if m not in existing:
|
|
99
|
+
merged.append(m)
|
|
100
|
+
existing.add(m)
|
|
101
|
+
return merged
|
|
102
|
+
|
|
103
|
+
async def start_refresh_loop(
|
|
104
|
+
self,
|
|
105
|
+
fetch_fn: Callable[[], Awaitable[List[str]]],
|
|
106
|
+
interval: int = 86400,
|
|
107
|
+
on_update: Optional[
|
|
108
|
+
Callable[[List[str]], Awaitable[None]]
|
|
109
|
+
] = None,
|
|
110
|
+
) -> None:
|
|
111
|
+
"""启动定时刷新循环(永久运行)。
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
fetch_fn: 返回远程列表的异步函数。
|
|
115
|
+
interval: 刷新间隔(秒)。
|
|
116
|
+
on_update: 更新回调。
|
|
117
|
+
"""
|
|
118
|
+
while True:
|
|
119
|
+
await self._do_refresh(fetch_fn, on_update)
|
|
120
|
+
await asyncio.sleep(interval)
|
|
121
|
+
|
|
122
|
+
async def _do_refresh(
|
|
123
|
+
self,
|
|
124
|
+
fetch_fn: Callable[[], Awaitable[List[str]]],
|
|
125
|
+
on_update: Optional[
|
|
126
|
+
Callable[[List[str]], Awaitable[None]]
|
|
127
|
+
] = None,
|
|
128
|
+
) -> None:
|
|
129
|
+
"""执行一次刷新。"""
|
|
130
|
+
if self._refreshing:
|
|
131
|
+
return
|
|
132
|
+
self._refreshing = True
|
|
133
|
+
try:
|
|
134
|
+
remote = await fetch_fn()
|
|
135
|
+
if remote:
|
|
136
|
+
merged = self._merge(remote)
|
|
137
|
+
self._items = merged
|
|
138
|
+
await self.save(merged)
|
|
139
|
+
if on_update is not None:
|
|
140
|
+
await on_update(merged)
|
|
141
|
+
logger.info(
|
|
142
|
+
"[%s] 列表已刷新: %d 项", self._name, len(merged)
|
|
143
|
+
)
|
|
144
|
+
except Exception as e:
|
|
145
|
+
logger.warning("[%s] 列表刷新失败: %s", self._name, e)
|
|
146
|
+
finally:
|
|
147
|
+
self._refreshing = False
|
|
148
|
+
|
|
149
|
+
@property
|
|
150
|
+
def items(self) -> List[str]:
|
|
151
|
+
"""当前列表副本。"""
|
|
152
|
+
return list(self._items)
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
"""带 TTL 的内存缓存。"""
|
|
4
|
+
|
|
5
|
+
import time
|
|
6
|
+
from typing import Any, Dict, Optional, Tuple
|
|
7
|
+
|
|
8
|
+
__all__ = ["MemoryCache"]
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class MemoryCache:
|
|
12
|
+
"""简单的 TTL 内存缓存。"""
|
|
13
|
+
|
|
14
|
+
def __init__(self, default_ttl: float = 0.0) -> None:
|
|
15
|
+
"""初始化缓存。
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
default_ttl: 默认存活秒数,0 表示永不过期。
|
|
19
|
+
"""
|
|
20
|
+
self._store: Dict[str, Tuple[Any, float]] = {}
|
|
21
|
+
self._default_ttl = default_ttl
|
|
22
|
+
|
|
23
|
+
def set(self, key: str, value: Any, ttl: Optional[float] = None) -> None:
|
|
24
|
+
"""写入缓存。
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
key: 键。
|
|
28
|
+
value: 值。
|
|
29
|
+
ttl: 存活秒数,None 用默认。
|
|
30
|
+
"""
|
|
31
|
+
effective = self._default_ttl if ttl is None else ttl
|
|
32
|
+
expire = 0.0 if effective <= 0 else time.time() + effective
|
|
33
|
+
self._store[key] = (value, expire)
|
|
34
|
+
|
|
35
|
+
def get(self, key: str, default: Any = None) -> Any:
|
|
36
|
+
"""读取缓存。
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
key: 键。
|
|
40
|
+
default: 缺省值。
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
值或默认值(过期自动剔除)。
|
|
44
|
+
"""
|
|
45
|
+
item = self._store.get(key)
|
|
46
|
+
if item is None:
|
|
47
|
+
return default
|
|
48
|
+
value, expire = item
|
|
49
|
+
if expire and time.time() > expire:
|
|
50
|
+
self._store.pop(key, None)
|
|
51
|
+
return default
|
|
52
|
+
return value
|
|
53
|
+
|
|
54
|
+
def delete(self, key: str) -> None:
|
|
55
|
+
"""删除键。"""
|
|
56
|
+
self._store.pop(key, None)
|
|
57
|
+
|
|
58
|
+
def clear(self) -> None:
|
|
59
|
+
"""清空缓存。"""
|
|
60
|
+
self._store.clear()
|
|
61
|
+
|
|
62
|
+
def cleanup(self) -> int:
|
|
63
|
+
"""清理过期项,返回清理数量。"""
|
|
64
|
+
now = time.time()
|
|
65
|
+
expired = [
|
|
66
|
+
k
|
|
67
|
+
for k, (_, exp) in self._store.items()
|
|
68
|
+
if exp and now > exp
|
|
69
|
+
]
|
|
70
|
+
for k in expired:
|
|
71
|
+
self._store.pop(k, None)
|
|
72
|
+
return len(expired)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
"""config 模块导出。"""
|
|
4
|
+
|
|
5
|
+
from echotools.config.base import ConfigBase
|
|
6
|
+
from echotools.config.center import ConfigCenter
|
|
7
|
+
from echotools.config.loader import (
|
|
8
|
+
find_config,
|
|
9
|
+
find_template,
|
|
10
|
+
load_file,
|
|
11
|
+
write_toml,
|
|
12
|
+
)
|
|
13
|
+
from echotools.config.merge import merge_dicts
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"ConfigBase",
|
|
17
|
+
"ConfigCenter",
|
|
18
|
+
"load_file",
|
|
19
|
+
"find_config",
|
|
20
|
+
"find_template",
|
|
21
|
+
"write_toml",
|
|
22
|
+
"merge_dicts",
|
|
23
|
+
]
|