athena-kit 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.
- athena_kit-1.0.0/LICENSE +21 -0
- athena_kit-1.0.0/PKG-INFO +245 -0
- athena_kit-1.0.0/README.md +210 -0
- athena_kit-1.0.0/pyproject.toml +90 -0
- athena_kit-1.0.0/src/athena_kit/__init__.py +5 -0
- athena_kit-1.0.0/src/athena_kit/_import_utils.py +41 -0
- athena_kit-1.0.0/src/athena_kit/bosun/__init__.py +32 -0
- athena_kit-1.0.0/src/athena_kit/bosun/ast/__init__.py +71 -0
- athena_kit-1.0.0/src/athena_kit/bosun/ast/nodes.py +115 -0
- athena_kit-1.0.0/src/athena_kit/bosun/ast/queries.py +130 -0
- athena_kit-1.0.0/src/athena_kit/bosun/opentsdb/__init__.py +95 -0
- athena_kit-1.0.0/src/athena_kit/bosun/opentsdb/enums.py +38 -0
- athena_kit-1.0.0/src/athena_kit/bosun/opentsdb/exceptions.py +2 -0
- athena_kit-1.0.0/src/athena_kit/bosun/opentsdb/intervals.py +45 -0
- athena_kit-1.0.0/src/athena_kit/bosun/opentsdb/models.py +205 -0
- athena_kit-1.0.0/src/athena_kit/bosun/opentsdb/parser.py +303 -0
- athena_kit-1.0.0/src/athena_kit/bosun/opentsdb/serializer.py +87 -0
- athena_kit-1.0.0/src/athena_kit/bosun/parser/__init__.py +43 -0
- athena_kit-1.0.0/src/athena_kit/bosun/parser/exceptions.py +7 -0
- athena_kit-1.0.0/src/athena_kit/bosun/parser/lexer.py +237 -0
- athena_kit-1.0.0/src/athena_kit/bosun/parser/parselets.py +173 -0
- athena_kit-1.0.0/src/athena_kit/bosun/parser/parser.py +46 -0
- athena_kit-1.0.0/src/athena_kit/bosun/parser/preprocess.py +399 -0
- athena_kit-1.0.0/src/athena_kit/bosun/parser/tokens.py +108 -0
- athena_kit-1.0.0/src/athena_kit/core/__init__.py +211 -0
- athena_kit-1.0.0/src/athena_kit/core/models/__init__.py +30 -0
- athena_kit-1.0.0/src/athena_kit/core/models/base.py +11 -0
- athena_kit-1.0.0/src/athena_kit/core/models/enums.py +114 -0
- athena_kit-1.0.0/src/athena_kit/core/tabular/__init__.py +55 -0
- athena_kit-1.0.0/src/athena_kit/core/tabular/backend.py +120 -0
- athena_kit-1.0.0/src/athena_kit/core/tabular/cell.py +22 -0
- athena_kit-1.0.0/src/athena_kit/core/tabular/pandas.py +139 -0
- athena_kit-1.0.0/src/athena_kit/core/tabular/repository.py +134 -0
- athena_kit-1.0.0/src/athena_kit/core/tabular/row.py +90 -0
- athena_kit-1.0.0/src/athena_kit/core/tabular/serialization.py +204 -0
- athena_kit-1.0.0/src/athena_kit/core/tabular/source.py +26 -0
- athena_kit-1.0.0/src/athena_kit/core/temporal/__init__.py +78 -0
- athena_kit-1.0.0/src/athena_kit/core/temporal/codec/__init__.py +90 -0
- athena_kit-1.0.0/src/athena_kit/core/temporal/codec/date.py +179 -0
- athena_kit-1.0.0/src/athena_kit/core/temporal/codec/datetime.py +204 -0
- athena_kit-1.0.0/src/athena_kit/core/temporal/codec/options.py +95 -0
- athena_kit-1.0.0/src/athena_kit/core/temporal/codec/temporal.py +141 -0
- athena_kit-1.0.0/src/athena_kit/core/temporal/codec/time.py +140 -0
- athena_kit-1.0.0/src/athena_kit/core/temporal/normalize.py +53 -0
- athena_kit-1.0.0/src/athena_kit/core/temporal/predicates.py +14 -0
- athena_kit-1.0.0/src/athena_kit/core/temporal/timezone.py +87 -0
- athena_kit-1.0.0/src/athena_kit/core/temporal/types.py +64 -0
- athena_kit-1.0.0/src/athena_kit/core/values/__init__.py +53 -0
- athena_kit-1.0.0/src/athena_kit/core/values/fallbacks.py +60 -0
- athena_kit-1.0.0/src/athena_kit/core/values/optional.py +81 -0
- athena_kit-1.0.0/src/athena_kit/core/values/predicates.py +0 -0
- athena_kit-1.0.0/src/athena_kit/http/__init__.py +76 -0
- athena_kit-1.0.0/src/athena_kit/http/aclient.py +38 -0
- athena_kit-1.0.0/src/athena_kit/http/client.py +38 -0
- athena_kit-1.0.0/src/athena_kit/http/hooks/__init__.py +79 -0
- athena_kit-1.0.0/src/athena_kit/http/hooks/event_hooks.py +105 -0
- athena_kit-1.0.0/src/athena_kit/http/hooks/logging.py +106 -0
- athena_kit-1.0.0/src/athena_kit/http/hooks/request_id.py +48 -0
- athena_kit-1.0.0/src/athena_kit/http/hooks/response_status.py +58 -0
- athena_kit-1.0.0/src/athena_kit/http/hooks/types.py +19 -0
- athena_kit-1.0.0/src/athena_kit/http/response_json.py +257 -0
- athena_kit-1.0.0/src/athena_kit/http/retrying.py +169 -0
- athena_kit-1.0.0/src/athena_kit/lark/__init__.py +28 -0
- athena_kit-1.0.0/src/athena_kit/lark/aclient.py +65 -0
- athena_kit-1.0.0/src/athena_kit/lark/auth.py +91 -0
- athena_kit-1.0.0/src/athena_kit/lark/sheets/__init__.py +30 -0
- athena_kit-1.0.0/src/athena_kit/lark/sheets/a1_notation.py +66 -0
- athena_kit-1.0.0/src/athena_kit/lark/sheets/aclient.py +343 -0
- athena_kit-1.0.0/src/athena_kit/lark/sheets/backend.py +64 -0
- athena_kit-1.0.0/src/athena_kit/lark/sheets/requests.py +63 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/__init__.py +2 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/adapters/__init__.py +35 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/adapters/styles.py +129 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/datas/__init__.py +34 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/datas/categorical.py +56 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/datas/xy.py +63 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/decorations/__init__.py +39 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/decorations/axis.py +34 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/decorations/cartesian.py +126 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/decorations/chart.py +26 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/decorations/grid.py +24 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/decorations/tick.py +58 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/options/__init__.py +38 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/options/_base.py +11 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/options/chart.py +38 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/options/coords/__init__.py +47 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/options/coords/axis.py +95 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/options/coords/cartesian.py +48 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/options/coords/grid.py +52 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/options/coords/legend.py +37 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/options/coords/tick.py +104 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/options/figure.py +28 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/options/plots/__init__.py +62 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/options/plots/bar.py +105 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/options/plots/data_label.py +89 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/options/plots/line.py +42 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/options/plots/marker.py +50 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/options/renderfig.py +57 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/options/rules/__init__.py +42 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/options/rules/conditions.py +73 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/options/rules/data_context.py +43 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/options/rules/matches.py +53 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/options/savefig.py +80 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/rendering/__init__.py +30 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/rendering/chart.py +21 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/rendering/color_cycle.py +17 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/rendering/coords/__init__.py +25 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/rendering/coords/_axes_runtime.py +80 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/rendering/coords/_render_plan.py +122 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/rendering/coords/cartesian.py +59 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/rendering/coords/legend.py +39 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/rendering/coords/tick.py +119 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/rendering/plots/__init__.py +25 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/rendering/plots/bar_plot.py +169 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/rendering/plots/layers/__init__.py +33 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/rendering/plots/layers/bar_layer.py +56 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/rendering/plots/layers/datalabel_layer.py +120 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/rendering/plots/layers/line_layer.py +52 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/rendering/plots/layers/marker_layer.py +119 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/rendering/plots/line_plot.py +63 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/runtime/__init__.py +64 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/runtime/artifact.py +62 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/runtime/context.py +220 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/runtime/pipeline.py +40 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/runtime/renderer.py +107 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/runtime/writers.py +99 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/specs/__init__.py +34 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/specs/_base.py +12 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/specs/chart.py +51 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/specs/coords/__init__.py +49 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/specs/coords/base.py +27 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/specs/coords/cartesian.py +121 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/specs/coords/polar.py +62 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/specs/coords/tick.py +94 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/specs/coords/unions.py +44 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/specs/figure.py +104 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/specs/plots/__init__.py +41 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/specs/plots/bar.py +53 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/specs/plots/base.py +16 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/specs/plots/line.py +51 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/specs/plots/pie.py +27 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/specs/plots/unions.py +31 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/styles/__init__.py +57 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/styles/base.py +23 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/styles/chart.py +28 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/styles/coord.py +31 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/styles/figure.py +34 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/styles/grid.py +38 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/styles/legend.py +75 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/styles/plot.py +31 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/styles/presets/__init__.py +30 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/styles/presets/fonts.py +15 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/styles/presets/palettes.py +16 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/styles/theme.py +58 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/styles/tick.py +69 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/transforms/__init__.py +0 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/transforms/alignment_data.py +120 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/transforms/normalize_data.py +131 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/transforms/tick_labels.py +101 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/types/__init__.py +92 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/types/options.py +37 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/types/specs.py +124 -0
- athena_kit-1.0.0/src/athena_kit/matplotlib/types/styles.py +201 -0
- athena_kit-1.0.0/src/athena_kit/py.typed +1 -0
athena_kit-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 wangmu
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: athena-kit
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: A modular Python toolkit for Athena projects.
|
|
5
|
+
Keywords: athena,toolkit,utilities,temporal,pydantic
|
|
6
|
+
Author: wangmu
|
|
7
|
+
Author-email: wangmu <wangmu8889@gmail.com>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
16
|
+
Classifier: Typing :: Typed
|
|
17
|
+
Requires-Dist: pydantic>=2.13.4
|
|
18
|
+
Requires-Dist: httpx>=0.28.1 ; extra == 'all'
|
|
19
|
+
Requires-Dist: matplotlib>=3.10.9 ; extra == 'all'
|
|
20
|
+
Requires-Dist: pandas>=2.3.3 ; extra == 'all'
|
|
21
|
+
Requires-Dist: pandas>=2.3.3 ; extra == 'dataframe'
|
|
22
|
+
Requires-Dist: httpx>=0.28.1 ; extra == 'http'
|
|
23
|
+
Requires-Dist: httpx>=0.28.1 ; extra == 'lark'
|
|
24
|
+
Requires-Dist: matplotlib>=3.10.9 ; extra == 'matplotlib'
|
|
25
|
+
Requires-Python: >=3.12
|
|
26
|
+
Project-URL: Homepage, https://github.com/wangmu0115/Athena
|
|
27
|
+
Project-URL: Repository, https://github.com/wangmu0115/Athena
|
|
28
|
+
Project-URL: Issues, https://github.com/wangmu0115/Athena/issues
|
|
29
|
+
Provides-Extra: all
|
|
30
|
+
Provides-Extra: dataframe
|
|
31
|
+
Provides-Extra: http
|
|
32
|
+
Provides-Extra: lark
|
|
33
|
+
Provides-Extra: matplotlib
|
|
34
|
+
Description-Content-Type: text/markdown
|
|
35
|
+
|
|
36
|
+
# Athena Kit
|
|
37
|
+
|
|
38
|
+
Athena Kit 是 Athena 项目使用的模块化 Python 工具包。
|
|
39
|
+
|
|
40
|
+
它发布为一个 Python 包:`athena-kit`,并提供统一的 Python 命名空间:`athena_kit`。
|
|
41
|
+
|
|
42
|
+
## 安装
|
|
43
|
+
|
|
44
|
+
```shell
|
|
45
|
+
uv add athena-kit
|
|
46
|
+
uv add "athena-kit[http]"
|
|
47
|
+
uv add "athena-kit[lark]"
|
|
48
|
+
uv add "athena-kit[matplotlib]"
|
|
49
|
+
uv add "athena-kit[dataframe]"
|
|
50
|
+
uv add "athena-kit[all]"
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Usage
|
|
54
|
+
|
|
55
|
+
### athena http
|
|
56
|
+
|
|
57
|
+
`athena_kit.http` 基于 HTTPX 做薄封装,提供同步/异步客户端、常用 event hooks、JSON 响应提取和简单重试工具。
|
|
58
|
+
|
|
59
|
+
同步请求:
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
from athena_kit.http import HttpClient
|
|
63
|
+
|
|
64
|
+
with HttpClient(base_url="https://api.example.test", request_id=True) as client:
|
|
65
|
+
response = client.get("/items")
|
|
66
|
+
response.raise_for_status()
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
异步请求:
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
from athena_kit.http import AsyncHttpClient
|
|
73
|
+
|
|
74
|
+
async with AsyncHttpClient(base_url="https://api.example.test", request_id=True) as client:
|
|
75
|
+
response = await client.get("/items")
|
|
76
|
+
response.raise_for_status()
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
启用请求 ID、日志和响应状态检查:
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
from athena_kit.http import HttpClient
|
|
83
|
+
|
|
84
|
+
with HttpClient(
|
|
85
|
+
base_url="https://api.example.test",
|
|
86
|
+
request_id=True,
|
|
87
|
+
logging=True,
|
|
88
|
+
response_status=True,
|
|
89
|
+
) as client:
|
|
90
|
+
response = client.get("/items")
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
提取 JSON 响应中的业务数据:
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
from athena_kit.http import create_biz_code_validator, extract_response_json_value
|
|
97
|
+
|
|
98
|
+
data = extract_response_json_value(
|
|
99
|
+
response,
|
|
100
|
+
"data.items[0]",
|
|
101
|
+
validator=create_biz_code_validator(code_key="code", success_codes=(0,)),
|
|
102
|
+
)
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
为请求增加简单重试:
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
import httpx
|
|
109
|
+
from athena_kit.http import RetryOptions, retry
|
|
110
|
+
|
|
111
|
+
response = retry(
|
|
112
|
+
request=lambda: client.get("/items"),
|
|
113
|
+
options=RetryOptions(attempts=3, initial_delay=0.2, multiplier=2.0, jitter=0.1, logger=True),
|
|
114
|
+
should_retry_result=lambda response: response.status_code in {429, 500, 502, 503, 504},
|
|
115
|
+
should_retry_exception=lambda exc: isinstance(exc, httpx.TimeoutException),
|
|
116
|
+
)
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
更多 HTTP 使用案例见 [docs/athena_http.md](docs/athena_http.md)。
|
|
120
|
+
|
|
121
|
+
### athena tabular
|
|
122
|
+
|
|
123
|
+
`athena_kit.core.tabular` 提供二维表格模型、单元格序列化、pandas DataFrame 转换,以及同步/异步表格后端和仓储抽象。
|
|
124
|
+
|
|
125
|
+
定义一行表格模型:
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
from datetime import date
|
|
129
|
+
|
|
130
|
+
from athena_kit.core.tabular import TableCell, TableRow
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
class TradeRow(TableRow):
|
|
134
|
+
trade_date: date = TableCell(title="日期", order=1)
|
|
135
|
+
symbol: str = TableCell(title="代码", order=2)
|
|
136
|
+
amount: int = TableCell(title="成交额", order=3)
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
模型和二维表格行互相转换:
|
|
140
|
+
|
|
141
|
+
```python
|
|
142
|
+
row = TradeRow(trade_date=date(2026, 6, 11), symbol="000001", amount=1200)
|
|
143
|
+
|
|
144
|
+
assert TradeRow.table_headers() == ["日期", "代码", "成交额"]
|
|
145
|
+
assert row.to_table_row() == ["2026-06-11", "000001", 1200]
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
使用 repository 接入具体二维表格后端:
|
|
149
|
+
|
|
150
|
+
```python
|
|
151
|
+
from athena_kit.core.tabular import TableRepository
|
|
152
|
+
|
|
153
|
+
repository = TableRepository(backend, locator, TradeRow)
|
|
154
|
+
rows = repository.list_all()
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
更多 Tabular 使用案例见 [docs/athena_tabular.md](docs/athena_tabular.md)。
|
|
158
|
+
|
|
159
|
+
### athena lark
|
|
160
|
+
|
|
161
|
+
`athena_kit.lark` 提供飞书开放平台异步客户端。当前重点支持 Sheets,包括创建电子表格、批量新增工作表、读写单个工作表范围,以及接入 `core.tabular` 的异步表格后端。
|
|
162
|
+
|
|
163
|
+
创建飞书客户端并新增电子表格:
|
|
164
|
+
|
|
165
|
+
```python
|
|
166
|
+
from athena_kit.lark import AsyncLarkClient
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
async with AsyncLarkClient(app_id="cli_xxx", app_secret="xxx", response_status=True) as client:
|
|
170
|
+
spreadsheet_token, url = await client.sheets.create_spreadsheet(
|
|
171
|
+
folder_token="fldcn_xxx",
|
|
172
|
+
title="交易汇总",
|
|
173
|
+
)
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
写入飞书工作表:
|
|
177
|
+
|
|
178
|
+
```python
|
|
179
|
+
revision, updated_rows, updated_columns = await client.sheets.overwrite_values(
|
|
180
|
+
spreadsheet_token=spreadsheet_token,
|
|
181
|
+
sheet_id="sheet_xxx",
|
|
182
|
+
headers=["日期", "代码", "成交额"],
|
|
183
|
+
rows_values=[["2026-06-11", "000001", 1200]],
|
|
184
|
+
start_row=1,
|
|
185
|
+
)
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
接入异步表格仓储:
|
|
189
|
+
|
|
190
|
+
```python
|
|
191
|
+
from athena_kit.core.tabular import AsyncTableRepository
|
|
192
|
+
from athena_kit.lark.sheets import AsyncLarkSheetsBackend, LarkSheetsLocator
|
|
193
|
+
|
|
194
|
+
backend = AsyncLarkSheetsBackend(client.sheets)
|
|
195
|
+
locator = LarkSheetsLocator(spreadsheet_token=spreadsheet_token, sheet_id="sheet_xxx")
|
|
196
|
+
repository = AsyncTableRepository(backend, locator, TradeRow)
|
|
197
|
+
|
|
198
|
+
rows = await repository.list_all()
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
更多 Lark Sheets 使用案例见 [docs/athena_lark.md](docs/athena_lark.md)。
|
|
202
|
+
|
|
203
|
+
## 模块结构
|
|
204
|
+
|
|
205
|
+
- `athena_kit.core`:基础模型、时间编解码、表格和通用值处理工具。
|
|
206
|
+
- `athena_kit.http`:基于 HTTPX 的同步/异步 HTTP 工具。
|
|
207
|
+
- `athena_kit.lark`:飞书开放平台异步客户端和 Sheets 工具。
|
|
208
|
+
- `athena_kit.matplotlib`:声明式图表渲染工具。
|
|
209
|
+
- `athena_kit.bosun`:Bosun 表达式解析和 OpenTSDB 查询工具。
|
|
210
|
+
|
|
211
|
+
## 开发
|
|
212
|
+
|
|
213
|
+
```shell
|
|
214
|
+
uv sync --extra all
|
|
215
|
+
uv run ruff check src tests examples
|
|
216
|
+
uv run ruff format --check src tests examples
|
|
217
|
+
uv run pytest tests
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
## 发布到 PyPI
|
|
221
|
+
|
|
222
|
+
发布前确认版本号已经更新,并且 `pyproject.toml` 和 `src/athena_kit/__init__.py` 中的版本一致。
|
|
223
|
+
|
|
224
|
+
```shell
|
|
225
|
+
uv sync --extra all
|
|
226
|
+
uv run ruff check src tests examples
|
|
227
|
+
uv run ruff format --check src tests examples
|
|
228
|
+
uv run pytest tests
|
|
229
|
+
rm -rf dist
|
|
230
|
+
uv build
|
|
231
|
+
uv publish
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
如果是第一次发布,先在 PyPI 创建 API Token,然后按 `uv publish` 的提示输入 token;也可以提前设置环境变量:
|
|
235
|
+
|
|
236
|
+
```shell
|
|
237
|
+
export UV_PUBLISH_TOKEN="pypi-..."
|
|
238
|
+
uv publish
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
发布完成后,在其他项目中安装:
|
|
242
|
+
|
|
243
|
+
```shell
|
|
244
|
+
uv add athena-kit
|
|
245
|
+
```
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
# Athena Kit
|
|
2
|
+
|
|
3
|
+
Athena Kit 是 Athena 项目使用的模块化 Python 工具包。
|
|
4
|
+
|
|
5
|
+
它发布为一个 Python 包:`athena-kit`,并提供统一的 Python 命名空间:`athena_kit`。
|
|
6
|
+
|
|
7
|
+
## 安装
|
|
8
|
+
|
|
9
|
+
```shell
|
|
10
|
+
uv add athena-kit
|
|
11
|
+
uv add "athena-kit[http]"
|
|
12
|
+
uv add "athena-kit[lark]"
|
|
13
|
+
uv add "athena-kit[matplotlib]"
|
|
14
|
+
uv add "athena-kit[dataframe]"
|
|
15
|
+
uv add "athena-kit[all]"
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Usage
|
|
19
|
+
|
|
20
|
+
### athena http
|
|
21
|
+
|
|
22
|
+
`athena_kit.http` 基于 HTTPX 做薄封装,提供同步/异步客户端、常用 event hooks、JSON 响应提取和简单重试工具。
|
|
23
|
+
|
|
24
|
+
同步请求:
|
|
25
|
+
|
|
26
|
+
```python
|
|
27
|
+
from athena_kit.http import HttpClient
|
|
28
|
+
|
|
29
|
+
with HttpClient(base_url="https://api.example.test", request_id=True) as client:
|
|
30
|
+
response = client.get("/items")
|
|
31
|
+
response.raise_for_status()
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
异步请求:
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
from athena_kit.http import AsyncHttpClient
|
|
38
|
+
|
|
39
|
+
async with AsyncHttpClient(base_url="https://api.example.test", request_id=True) as client:
|
|
40
|
+
response = await client.get("/items")
|
|
41
|
+
response.raise_for_status()
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
启用请求 ID、日志和响应状态检查:
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
from athena_kit.http import HttpClient
|
|
48
|
+
|
|
49
|
+
with HttpClient(
|
|
50
|
+
base_url="https://api.example.test",
|
|
51
|
+
request_id=True,
|
|
52
|
+
logging=True,
|
|
53
|
+
response_status=True,
|
|
54
|
+
) as client:
|
|
55
|
+
response = client.get("/items")
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
提取 JSON 响应中的业务数据:
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
from athena_kit.http import create_biz_code_validator, extract_response_json_value
|
|
62
|
+
|
|
63
|
+
data = extract_response_json_value(
|
|
64
|
+
response,
|
|
65
|
+
"data.items[0]",
|
|
66
|
+
validator=create_biz_code_validator(code_key="code", success_codes=(0,)),
|
|
67
|
+
)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
为请求增加简单重试:
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
import httpx
|
|
74
|
+
from athena_kit.http import RetryOptions, retry
|
|
75
|
+
|
|
76
|
+
response = retry(
|
|
77
|
+
request=lambda: client.get("/items"),
|
|
78
|
+
options=RetryOptions(attempts=3, initial_delay=0.2, multiplier=2.0, jitter=0.1, logger=True),
|
|
79
|
+
should_retry_result=lambda response: response.status_code in {429, 500, 502, 503, 504},
|
|
80
|
+
should_retry_exception=lambda exc: isinstance(exc, httpx.TimeoutException),
|
|
81
|
+
)
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
更多 HTTP 使用案例见 [docs/athena_http.md](docs/athena_http.md)。
|
|
85
|
+
|
|
86
|
+
### athena tabular
|
|
87
|
+
|
|
88
|
+
`athena_kit.core.tabular` 提供二维表格模型、单元格序列化、pandas DataFrame 转换,以及同步/异步表格后端和仓储抽象。
|
|
89
|
+
|
|
90
|
+
定义一行表格模型:
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
from datetime import date
|
|
94
|
+
|
|
95
|
+
from athena_kit.core.tabular import TableCell, TableRow
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class TradeRow(TableRow):
|
|
99
|
+
trade_date: date = TableCell(title="日期", order=1)
|
|
100
|
+
symbol: str = TableCell(title="代码", order=2)
|
|
101
|
+
amount: int = TableCell(title="成交额", order=3)
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
模型和二维表格行互相转换:
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
row = TradeRow(trade_date=date(2026, 6, 11), symbol="000001", amount=1200)
|
|
108
|
+
|
|
109
|
+
assert TradeRow.table_headers() == ["日期", "代码", "成交额"]
|
|
110
|
+
assert row.to_table_row() == ["2026-06-11", "000001", 1200]
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
使用 repository 接入具体二维表格后端:
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
from athena_kit.core.tabular import TableRepository
|
|
117
|
+
|
|
118
|
+
repository = TableRepository(backend, locator, TradeRow)
|
|
119
|
+
rows = repository.list_all()
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
更多 Tabular 使用案例见 [docs/athena_tabular.md](docs/athena_tabular.md)。
|
|
123
|
+
|
|
124
|
+
### athena lark
|
|
125
|
+
|
|
126
|
+
`athena_kit.lark` 提供飞书开放平台异步客户端。当前重点支持 Sheets,包括创建电子表格、批量新增工作表、读写单个工作表范围,以及接入 `core.tabular` 的异步表格后端。
|
|
127
|
+
|
|
128
|
+
创建飞书客户端并新增电子表格:
|
|
129
|
+
|
|
130
|
+
```python
|
|
131
|
+
from athena_kit.lark import AsyncLarkClient
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
async with AsyncLarkClient(app_id="cli_xxx", app_secret="xxx", response_status=True) as client:
|
|
135
|
+
spreadsheet_token, url = await client.sheets.create_spreadsheet(
|
|
136
|
+
folder_token="fldcn_xxx",
|
|
137
|
+
title="交易汇总",
|
|
138
|
+
)
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
写入飞书工作表:
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
revision, updated_rows, updated_columns = await client.sheets.overwrite_values(
|
|
145
|
+
spreadsheet_token=spreadsheet_token,
|
|
146
|
+
sheet_id="sheet_xxx",
|
|
147
|
+
headers=["日期", "代码", "成交额"],
|
|
148
|
+
rows_values=[["2026-06-11", "000001", 1200]],
|
|
149
|
+
start_row=1,
|
|
150
|
+
)
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
接入异步表格仓储:
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
from athena_kit.core.tabular import AsyncTableRepository
|
|
157
|
+
from athena_kit.lark.sheets import AsyncLarkSheetsBackend, LarkSheetsLocator
|
|
158
|
+
|
|
159
|
+
backend = AsyncLarkSheetsBackend(client.sheets)
|
|
160
|
+
locator = LarkSheetsLocator(spreadsheet_token=spreadsheet_token, sheet_id="sheet_xxx")
|
|
161
|
+
repository = AsyncTableRepository(backend, locator, TradeRow)
|
|
162
|
+
|
|
163
|
+
rows = await repository.list_all()
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
更多 Lark Sheets 使用案例见 [docs/athena_lark.md](docs/athena_lark.md)。
|
|
167
|
+
|
|
168
|
+
## 模块结构
|
|
169
|
+
|
|
170
|
+
- `athena_kit.core`:基础模型、时间编解码、表格和通用值处理工具。
|
|
171
|
+
- `athena_kit.http`:基于 HTTPX 的同步/异步 HTTP 工具。
|
|
172
|
+
- `athena_kit.lark`:飞书开放平台异步客户端和 Sheets 工具。
|
|
173
|
+
- `athena_kit.matplotlib`:声明式图表渲染工具。
|
|
174
|
+
- `athena_kit.bosun`:Bosun 表达式解析和 OpenTSDB 查询工具。
|
|
175
|
+
|
|
176
|
+
## 开发
|
|
177
|
+
|
|
178
|
+
```shell
|
|
179
|
+
uv sync --extra all
|
|
180
|
+
uv run ruff check src tests examples
|
|
181
|
+
uv run ruff format --check src tests examples
|
|
182
|
+
uv run pytest tests
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
## 发布到 PyPI
|
|
186
|
+
|
|
187
|
+
发布前确认版本号已经更新,并且 `pyproject.toml` 和 `src/athena_kit/__init__.py` 中的版本一致。
|
|
188
|
+
|
|
189
|
+
```shell
|
|
190
|
+
uv sync --extra all
|
|
191
|
+
uv run ruff check src tests examples
|
|
192
|
+
uv run ruff format --check src tests examples
|
|
193
|
+
uv run pytest tests
|
|
194
|
+
rm -rf dist
|
|
195
|
+
uv build
|
|
196
|
+
uv publish
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
如果是第一次发布,先在 PyPI 创建 API Token,然后按 `uv publish` 的提示输入 token;也可以提前设置环境变量:
|
|
200
|
+
|
|
201
|
+
```shell
|
|
202
|
+
export UV_PUBLISH_TOKEN="pypi-..."
|
|
203
|
+
uv publish
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
发布完成后,在其他项目中安装:
|
|
207
|
+
|
|
208
|
+
```shell
|
|
209
|
+
uv add athena-kit
|
|
210
|
+
```
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "athena-kit"
|
|
3
|
+
version = "1.0.0"
|
|
4
|
+
description = "A modular Python toolkit for Athena projects."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
authors = [
|
|
7
|
+
{ name = "wangmu", email = "wangmu8889@gmail.com" }
|
|
8
|
+
]
|
|
9
|
+
requires-python = ">=3.12"
|
|
10
|
+
dependencies = [
|
|
11
|
+
"pydantic>=2.13.4",
|
|
12
|
+
]
|
|
13
|
+
license = "MIT"
|
|
14
|
+
license-files = ["LICENSE"]
|
|
15
|
+
keywords = ["athena", "toolkit", "utilities", "temporal", "pydantic"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 4 - Beta",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"Operating System :: OS Independent",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3.12",
|
|
22
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
23
|
+
"Typing :: Typed",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
[project.urls]
|
|
27
|
+
Homepage = "https://github.com/wangmu0115/Athena"
|
|
28
|
+
Repository = "https://github.com/wangmu0115/Athena"
|
|
29
|
+
Issues = "https://github.com/wangmu0115/Athena/issues"
|
|
30
|
+
|
|
31
|
+
[project.optional-dependencies]
|
|
32
|
+
dataframe = [
|
|
33
|
+
"pandas>=2.3.3",
|
|
34
|
+
]
|
|
35
|
+
http = [
|
|
36
|
+
"httpx>=0.28.1",
|
|
37
|
+
]
|
|
38
|
+
lark = [
|
|
39
|
+
"httpx>=0.28.1",
|
|
40
|
+
]
|
|
41
|
+
matplotlib = [
|
|
42
|
+
"matplotlib>=3.10.9",
|
|
43
|
+
]
|
|
44
|
+
all = [
|
|
45
|
+
"httpx>=0.28.1",
|
|
46
|
+
"matplotlib>=3.10.9",
|
|
47
|
+
"pandas>=2.3.3",
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
[build-system]
|
|
51
|
+
requires = ["uv_build>=0.9.25,<0.12.0"]
|
|
52
|
+
build-backend = "uv_build"
|
|
53
|
+
|
|
54
|
+
[dependency-groups]
|
|
55
|
+
dev = [
|
|
56
|
+
"pytest>=9.0.3",
|
|
57
|
+
"ruff>=0.15.12",
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
[tool.ruff]
|
|
61
|
+
line-length = 120
|
|
62
|
+
target-version = "py312"
|
|
63
|
+
src = [
|
|
64
|
+
"src",
|
|
65
|
+
"tests",
|
|
66
|
+
"examples",
|
|
67
|
+
]
|
|
68
|
+
|
|
69
|
+
[tool.ruff.lint]
|
|
70
|
+
select = [
|
|
71
|
+
"E", # pycodestyle error
|
|
72
|
+
"F", # pyflakes
|
|
73
|
+
"I", # isort
|
|
74
|
+
"UP", # pyupgrade
|
|
75
|
+
"B", # flake8-bugbear
|
|
76
|
+
"SIM", # flake8-simplify
|
|
77
|
+
]
|
|
78
|
+
ignore = []
|
|
79
|
+
|
|
80
|
+
[tool.ruff.format]
|
|
81
|
+
quote-style = "double"
|
|
82
|
+
indent-style = "space"
|
|
83
|
+
line-ending = "auto"
|
|
84
|
+
docstring-code-format = true
|
|
85
|
+
preview = true
|
|
86
|
+
|
|
87
|
+
[tool.ruff.lint.isort]
|
|
88
|
+
known-first-party = [
|
|
89
|
+
"athena_kit",
|
|
90
|
+
]
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
from importlib import import_module
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def import_attr(
|
|
5
|
+
attr_name: str,
|
|
6
|
+
module_name: str | None,
|
|
7
|
+
package: str | None,
|
|
8
|
+
) -> object:
|
|
9
|
+
"""Import an attribute from a module located in a package.
|
|
10
|
+
|
|
11
|
+
This utility function is used in custom `__getattr__` methods within `__init__.py`
|
|
12
|
+
files to dynamically import attributes.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
attr_name: The name of the attribute to import.
|
|
16
|
+
module_name: The name of the module to import from.
|
|
17
|
+
|
|
18
|
+
If `None`, the attribute is imported from the package itself.
|
|
19
|
+
package: The name of the package where the module is located.
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
The imported attribute.
|
|
23
|
+
|
|
24
|
+
Raises:
|
|
25
|
+
ImportError: If the module cannot be found.
|
|
26
|
+
AttributeError: If the attribute does not exist in the module or package.
|
|
27
|
+
"""
|
|
28
|
+
if module_name == "__module__" or module_name is None:
|
|
29
|
+
try:
|
|
30
|
+
result = import_module(f".{attr_name}", package=package)
|
|
31
|
+
except ModuleNotFoundError:
|
|
32
|
+
msg = f"module '{package!r}' has no attribute {attr_name!r}"
|
|
33
|
+
raise AttributeError(msg) from None
|
|
34
|
+
else:
|
|
35
|
+
try:
|
|
36
|
+
module = import_module(f".{module_name}", package=package)
|
|
37
|
+
except ModuleNotFoundError as err:
|
|
38
|
+
msg = f"module '{package!r}.{module_name!r}' not found ({err})"
|
|
39
|
+
raise ImportError(msg) from None
|
|
40
|
+
result = getattr(module, attr_name)
|
|
41
|
+
return result
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING
|
|
2
|
+
|
|
3
|
+
from athena_kit._import_utils import import_attr
|
|
4
|
+
|
|
5
|
+
if TYPE_CHECKING:
|
|
6
|
+
from athena_kit.bosun.parser import Lexer, Parser
|
|
7
|
+
from athena_kit.bosun.parser.preprocess import preprocess
|
|
8
|
+
|
|
9
|
+
__all__ = (
|
|
10
|
+
"Lexer",
|
|
11
|
+
"Parser",
|
|
12
|
+
"preprocess",
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
_dynamic_imports = {
|
|
16
|
+
"Lexer": "parser",
|
|
17
|
+
"Parser": "parser",
|
|
18
|
+
"preprocess": "parser.preprocess",
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def __getattr__(attr_name: str) -> object:
|
|
23
|
+
module_name = _dynamic_imports.get(attr_name)
|
|
24
|
+
if module_name is None:
|
|
25
|
+
raise AttributeError(f"module {__name__!r} has no attribute {attr_name!r}")
|
|
26
|
+
result = import_attr(attr_name, module_name, __spec__.parent)
|
|
27
|
+
globals()[attr_name] = result
|
|
28
|
+
return result
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def __dir__() -> list[str]:
|
|
32
|
+
return list(__all__)
|