finlab-sentinel 0.1.5__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.
Files changed (51) hide show
  1. finlab_sentinel-0.1.5/.github/workflows/ci.yml +77 -0
  2. finlab_sentinel-0.1.5/.github/workflows/release.yml +47 -0
  3. finlab_sentinel-0.1.5/.gitignore +146 -0
  4. finlab_sentinel-0.1.5/PKG-INFO +272 -0
  5. finlab_sentinel-0.1.5/README.md +233 -0
  6. finlab_sentinel-0.1.5/pyproject.toml +118 -0
  7. finlab_sentinel-0.1.5/sentinel.toml.example +76 -0
  8. finlab_sentinel-0.1.5/src/finlab_sentinel/__init__.py +23 -0
  9. finlab_sentinel-0.1.5/src/finlab_sentinel/cli/__init__.py +5 -0
  10. finlab_sentinel-0.1.5/src/finlab_sentinel/cli/main.py +485 -0
  11. finlab_sentinel-0.1.5/src/finlab_sentinel/comparison/__init__.py +27 -0
  12. finlab_sentinel-0.1.5/src/finlab_sentinel/comparison/differ.py +524 -0
  13. finlab_sentinel-0.1.5/src/finlab_sentinel/comparison/hasher.py +172 -0
  14. finlab_sentinel-0.1.5/src/finlab_sentinel/comparison/policies.py +223 -0
  15. finlab_sentinel-0.1.5/src/finlab_sentinel/comparison/report.py +259 -0
  16. finlab_sentinel-0.1.5/src/finlab_sentinel/config/__init__.py +25 -0
  17. finlab_sentinel-0.1.5/src/finlab_sentinel/config/loader.py +222 -0
  18. finlab_sentinel-0.1.5/src/finlab_sentinel/config/schema.py +130 -0
  19. finlab_sentinel-0.1.5/src/finlab_sentinel/core/__init__.py +9 -0
  20. finlab_sentinel-0.1.5/src/finlab_sentinel/core/hooks.py +195 -0
  21. finlab_sentinel-0.1.5/src/finlab_sentinel/core/interceptor.py +287 -0
  22. finlab_sentinel-0.1.5/src/finlab_sentinel/core/patcher.py +151 -0
  23. finlab_sentinel-0.1.5/src/finlab_sentinel/core/registry.py +69 -0
  24. finlab_sentinel-0.1.5/src/finlab_sentinel/exceptions.py +43 -0
  25. finlab_sentinel-0.1.5/src/finlab_sentinel/handlers/__init__.py +17 -0
  26. finlab_sentinel-0.1.5/src/finlab_sentinel/handlers/base.py +43 -0
  27. finlab_sentinel-0.1.5/src/finlab_sentinel/handlers/callback.py +110 -0
  28. finlab_sentinel-0.1.5/src/finlab_sentinel/handlers/exception.py +39 -0
  29. finlab_sentinel-0.1.5/src/finlab_sentinel/handlers/warning.py +92 -0
  30. finlab_sentinel-0.1.5/src/finlab_sentinel/py.typed +0 -0
  31. finlab_sentinel-0.1.5/src/finlab_sentinel/storage/__init__.py +10 -0
  32. finlab_sentinel-0.1.5/src/finlab_sentinel/storage/backend.py +186 -0
  33. finlab_sentinel-0.1.5/src/finlab_sentinel/storage/cleanup.py +83 -0
  34. finlab_sentinel-0.1.5/src/finlab_sentinel/storage/index.py +338 -0
  35. finlab_sentinel-0.1.5/src/finlab_sentinel/storage/parquet.py +317 -0
  36. finlab_sentinel-0.1.5/tests/__init__.py +1 -0
  37. finlab_sentinel-0.1.5/tests/conftest.py +98 -0
  38. finlab_sentinel-0.1.5/tests/unit/__init__.py +1 -0
  39. finlab_sentinel-0.1.5/tests/unit/test_cleanup.py +171 -0
  40. finlab_sentinel-0.1.5/tests/unit/test_cli.py +382 -0
  41. finlab_sentinel-0.1.5/tests/unit/test_config.py +158 -0
  42. finlab_sentinel-0.1.5/tests/unit/test_differ.py +207 -0
  43. finlab_sentinel-0.1.5/tests/unit/test_handlers.py +173 -0
  44. finlab_sentinel-0.1.5/tests/unit/test_hasher.py +100 -0
  45. finlab_sentinel-0.1.5/tests/unit/test_hooks.py +235 -0
  46. finlab_sentinel-0.1.5/tests/unit/test_interceptor.py +418 -0
  47. finlab_sentinel-0.1.5/tests/unit/test_loader.py +202 -0
  48. finlab_sentinel-0.1.5/tests/unit/test_patcher.py +199 -0
  49. finlab_sentinel-0.1.5/tests/unit/test_policies.py +265 -0
  50. finlab_sentinel-0.1.5/tests/unit/test_report.py +227 -0
  51. finlab_sentinel-0.1.5/tests/unit/test_storage.py +260 -0
@@ -0,0 +1,77 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main, develop]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ concurrency:
10
+ group: ${{ github.workflow }}-${{ github.ref }}
11
+ cancel-in-progress: true
12
+
13
+ jobs:
14
+ lint:
15
+ runs-on: ubuntu-latest
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+
19
+ - name: Set up uv
20
+ uses: astral-sh/setup-uv@v3
21
+ with:
22
+ version: "latest"
23
+
24
+ - name: Set up Python
25
+ run: uv python install 3.12
26
+
27
+ - name: Install dependencies
28
+ run: uv sync --extra dev
29
+
30
+ - name: Run ruff linter
31
+ run: uv run ruff check src/ tests/
32
+
33
+ - name: Run ruff formatter check
34
+ run: uv run ruff format --check src/ tests/
35
+
36
+ - name: Run mypy type checker
37
+ run: uv run mypy src/
38
+
39
+ test:
40
+ runs-on: ${{ matrix.os }}
41
+ strategy:
42
+ fail-fast: false
43
+ matrix:
44
+ os: [ubuntu-latest, macos-latest, windows-latest]
45
+ python-version: ["3.10", "3.11", "3.12", "3.13"]
46
+
47
+ steps:
48
+ - uses: actions/checkout@v4
49
+
50
+ - name: Set up uv
51
+ uses: astral-sh/setup-uv@v3
52
+ with:
53
+ version: "latest"
54
+
55
+ - name: Set up Python ${{ matrix.python-version }}
56
+ run: uv python install ${{ matrix.python-version }}
57
+
58
+ - name: Install dependencies
59
+ run: uv sync --extra dev
60
+
61
+ - name: Run tests with coverage
62
+ run: uv run pytest tests/ -v --cov=src/finlab_sentinel --cov-report=xml --cov-report=html --cov-fail-under=80 --junitxml=junit/test-results-${{ matrix.python-version }}.xml
63
+
64
+ - name: Upload coverage reports to Codecov
65
+ if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.12'
66
+ uses: codecov/codecov-action@v5
67
+ with:
68
+ token: ${{ secrets.CODECOV_TOKEN }}
69
+
70
+ - name: Upload test results
71
+ uses: actions/upload-artifact@v4
72
+ if: always()
73
+ with:
74
+ name: test-results-${{ matrix.os }}-${{ matrix.python-version }}
75
+ path: |
76
+ junit/
77
+ htmlcov/
@@ -0,0 +1,47 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ workflow_dispatch: # Manual trigger only
5
+
6
+ jobs:
7
+ publish:
8
+ name: Build and Publish to PyPI
9
+ runs-on: ubuntu-latest
10
+ environment: pypi-publish # Requires environment approval
11
+ steps:
12
+ - name: Check out code
13
+ uses: actions/checkout@v4
14
+
15
+ - name: Install uv
16
+ uses: astral-sh/setup-uv@v3
17
+ with:
18
+ version: "latest"
19
+
20
+ - name: Set up Python
21
+ run: uv python install 3.12
22
+
23
+ - name: Install dependencies
24
+ run: uv sync --extra dev
25
+
26
+ - name: Verify project configuration
27
+ run: |
28
+ uv run python -c "import sys; sys.path.insert(0, 'src'); import finlab_sentinel; print(f'Package version: {finlab_sentinel.__version__}')"
29
+
30
+ - name: Build package
31
+ run: uv build
32
+
33
+ - name: Check package contents
34
+ run: uv run twine check dist/*
35
+
36
+ - name: Publish to PyPI
37
+ uses: pypa/gh-action-pypi-publish@release/v1
38
+ with:
39
+ password: ${{ secrets.PYPI_API_TOKEN }}
40
+ verbose: true
41
+
42
+ - name: Upload build artifacts
43
+ uses: actions/upload-artifact@v4
44
+ if: always()
45
+ with:
46
+ name: dist
47
+ path: dist/
@@ -0,0 +1,146 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ *.manifest
31
+ *.spec
32
+
33
+ # Installer logs
34
+ pip-log.txt
35
+ pip-delete-this-directory.txt
36
+
37
+ # Unit test / coverage reports
38
+ htmlcov/
39
+ .tox/
40
+ .nox/
41
+ .coverage
42
+ .coverage.*
43
+ .cache
44
+ nosetests.xml
45
+ coverage.xml
46
+ *.cover
47
+ *.py,cover
48
+ .hypothesis/
49
+ .pytest_cache/
50
+ junit/
51
+
52
+ # Translations
53
+ *.mo
54
+ *.pot
55
+
56
+ # Django stuff:
57
+ *.log
58
+ local_settings.py
59
+ db.sqlite3
60
+ db.sqlite3-journal
61
+
62
+ # Flask stuff:
63
+ instance/
64
+ .webassets-cache
65
+
66
+ # Scrapy stuff:
67
+ .scrapy
68
+
69
+ # Sphinx documentation
70
+ docs/_build/
71
+
72
+ # PyBuilder
73
+ .pybuilder/
74
+ target/
75
+
76
+ # Jupyter Notebook
77
+ .ipynb_checkpoints
78
+
79
+ # IPython
80
+ profile_default/
81
+ ipython_config.py
82
+
83
+ # pyenv
84
+ .python-version
85
+
86
+ # pipenv
87
+ Pipfile.lock
88
+
89
+ # PEP 582
90
+ __pypackages__/
91
+
92
+ # Celery stuff
93
+ celerybeat-schedule
94
+ celerybeat.pid
95
+
96
+ # SageMath parsed files
97
+ *.sage.py
98
+
99
+ # Environments
100
+ .env
101
+ .venv
102
+ env/
103
+ venv/
104
+ ENV/
105
+ env.bak/
106
+ venv.bak/
107
+
108
+ # Spyder project settings
109
+ .spyderproject
110
+ .spyproject
111
+
112
+ # Rope project settings
113
+ .ropeproject
114
+
115
+ # mkdocs documentation
116
+ /site
117
+
118
+ # mypy
119
+ .mypy_cache/
120
+ .dmypy.json
121
+ dmypy.json
122
+
123
+ # Pyre type checker
124
+ .pyre/
125
+
126
+ # pytype static type analyzer
127
+ .pytype/
128
+
129
+ # Cython debug symbols
130
+ cython_debug/
131
+
132
+ # IDE
133
+ .idea/
134
+ .vscode/
135
+ *.swp
136
+ *.swo
137
+ *~
138
+
139
+ # Local config
140
+ sentinel.toml
141
+
142
+ # uv
143
+ uv.lock
144
+
145
+ # finlab-sentinel specific
146
+ .finlab-sentinel/
@@ -0,0 +1,272 @@
1
+ Metadata-Version: 2.4
2
+ Name: finlab-sentinel
3
+ Version: 0.1.5
4
+ Summary: Defensive monitoring layer for finlab data.get API - detect unexpected data changes
5
+ Project-URL: Homepage, https://github.com/iapcal/finlab-sentinel
6
+ Project-URL: Repository, https://github.com/iapcal/finlab-sentinel
7
+ Project-URL: Issues, https://github.com/iapcal/finlab-sentinel/issues
8
+ Author-email: iapcal <chiyimin2018@gmail.com>
9
+ License: MIT
10
+ Keywords: backtesting,data-validation,finlab,monitoring,taiwan-stock
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Financial and Insurance Industry
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
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 :: Office/Business :: Financial :: Investment
21
+ Classifier: Typing :: Typed
22
+ Requires-Python: >=3.10
23
+ Requires-Dist: pandas>=2.0.0
24
+ Requires-Dist: pyarrow>=14.0.0
25
+ Requires-Dist: pydantic-settings>=2.0.0
26
+ Requires-Dist: pydantic>=2.0.0
27
+ Requires-Dist: rich>=13.0.0
28
+ Requires-Dist: typer>=0.12.0
29
+ Requires-Dist: xxhash>=3.0.0
30
+ Provides-Extra: dev
31
+ Requires-Dist: mypy>=1.10.0; extra == 'dev'
32
+ Requires-Dist: pandas-stubs>=2.0.0; extra == 'dev'
33
+ Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
34
+ Requires-Dist: pytest-mock>=3.0.0; extra == 'dev'
35
+ Requires-Dist: pytest>=8.0.0; extra == 'dev'
36
+ Requires-Dist: ruff>=0.4.0; extra == 'dev'
37
+ Requires-Dist: twine>=5.0.0; extra == 'dev'
38
+ Description-Content-Type: text/markdown
39
+
40
+ # finlab-sentinel
41
+
42
+ ![Python versions](https://img.shields.io/badge/python-3.10%20%7C%203.11%20%7C%203.12%20%7C%203.13-blue)
43
+ ![Windows](https://img.shields.io/badge/OS-Windows-0078D6?logo=windows&logoColor=white)
44
+ ![Linux](https://img.shields.io/badge/OS-Linux-FCC624?logo=linux&logoColor=black)
45
+ ![macOS](https://img.shields.io/badge/OS-macOS-000000?logo=apple&logoColor=white)
46
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
47
+ [![CI](https://github.com/iapcal/finlab-sentinel/actions/workflows/ci.yml/badge.svg)](https://github.com/iapcal/finlab-sentinel/actions/workflows/ci.yml)
48
+ [![coverage](https://img.shields.io/codecov/c/github/iapcal/finlab-sentinel)](https://codecov.io/gh/iapcal/finlab-sentinel)
49
+
50
+ **finlab-sentinel** 是 [finlab](https://github.com/finlab-python/finlab) 套件的防禦層,用於監控 `data.get` API 的資料變化,防止未預期的資料異動影響回測或選股結果。
51
+
52
+ ## 功能特色
53
+
54
+ - **自動比對**: 每次 `data.get` 時自動比對歷史資料
55
+ - **滾動備份**: 保留 7 天(可配置)的備份資料
56
+ - **智慧檢測**:
57
+ - 數值容差比對(可配置 rtol/atol)
58
+ - dtype 變更檢測
59
+ - NA 類型差異檢測(pd.NA vs np.nan vs None)
60
+ - **彈性政策**:
61
+ - `append_only`: 只允許新增,不允許刪除或修改歷史
62
+ - `threshold`: 允許小幅度變更(如 10% 以內)
63
+ - 黑名單配置:指定可修改歷史的資料集
64
+ - **可配置行為**:
65
+ - 拋出例外(預設)
66
+ - 警告並使用快取
67
+ - 警告並使用新資料
68
+ - **Preprocess Hook**: 比對前預處理(如四捨五入),支援萬用字元模式
69
+ - **通知機制**: 支援自訂 callback(如 LINE、email 通知)
70
+ - **CLI 工具**: 管理備份、查看差異、接受新資料
71
+
72
+ ## 安裝
73
+
74
+ ```bash
75
+ pip install finlab-sentinel
76
+ ```
77
+
78
+ 或使用 uv:
79
+
80
+ ```bash
81
+ uv add finlab-sentinel
82
+ ```
83
+
84
+ ## 快速開始
85
+
86
+ ```python
87
+ import finlab_sentinel
88
+
89
+ # 啟用 sentinel
90
+ finlab_sentinel.enable()
91
+
92
+ # 正常使用 finlab
93
+ from finlab import data
94
+ close = data.get('price:收盤價') # 自動備份並比對
95
+
96
+ # 如果資料異常,會根據配置拋出例外或警告
97
+ ```
98
+
99
+ ## 配置
100
+
101
+ 建立 `sentinel.toml` 檔案:
102
+
103
+ ```toml
104
+ [storage]
105
+ path = "~/.finlab-sentinel/"
106
+ retention_days = 7
107
+
108
+ [comparison]
109
+ rtol = 1e-5
110
+ change_threshold = 0.10
111
+
112
+ [comparison.policies]
113
+ default_mode = "append_only"
114
+ history_modifiable = ["fundamental_features:某些財報資料"]
115
+ # 允許 NA→有值 的轉換(例如:預估資料後來補上)
116
+ allow_na_to_value = ["price:收盤價"]
117
+
118
+ [anomaly]
119
+ behavior = "raise" # raise | warn_return_cached | warn_return_new
120
+ save_reports = true
121
+
122
+ # 可選:設定通知 callback
123
+ # callback = "myproject.notifications:send_line"
124
+ ```
125
+
126
+ ## CLI 使用
127
+
128
+ ```bash
129
+ # 列出所有備份
130
+ sentinel list
131
+
132
+ # 清理過期備份
133
+ sentinel cleanup --days 14
134
+
135
+ # 查看資料差異
136
+ sentinel diff "price:收盤價"
137
+
138
+ # 接受新資料作為基準
139
+ sentinel accept "price:收盤價" --reason "確認資料修正"
140
+
141
+ # 匯出備份
142
+ sentinel export "price:收盤價" -o ./backup.parquet
143
+ ```
144
+
145
+ ## 處理資料異常
146
+
147
+ 當檢測到資料異常時:
148
+
149
+ ```python
150
+ from finlab_sentinel import DataAnomalyError
151
+
152
+ try:
153
+ close = data.get('price:收盤價')
154
+ except DataAnomalyError as e:
155
+ print(f"資料異常: {e.report.summary}")
156
+ # 檢查報告詳情
157
+ print(f"變動比例: {e.report.comparison_result.change_ratio:.1%}")
158
+
159
+ # 如果確認要接受新資料
160
+ from finlab_sentinel.core.interceptor import accept_current_data
161
+ accept_current_data('price:收盤價', reason="確認資料修正")
162
+ ```
163
+
164
+ ## Preprocess Hook
165
+
166
+ Preprocess hook 讓你可以在比對前先對資料做預處理,例如四捨五入、排序欄位等。這在處理預期的浮點數精度差異時特別有用。
167
+
168
+ **注意**: 預處理只用於比對,回傳給使用者的永遠是原始資料。
169
+
170
+ ```python
171
+ import finlab_sentinel
172
+
173
+ # 註冊特定 dataset 的 preprocess hook
174
+ finlab_sentinel.register_preprocess_hook(
175
+ "price:收盤價",
176
+ lambda df: df.round(2) # 四捨五入到小數第二位
177
+ )
178
+
179
+ # 支援萬用字元模式
180
+ finlab_sentinel.register_preprocess_hook(
181
+ "price:*", # 符合所有 price: 開頭的 dataset
182
+ lambda df: df.round(2)
183
+ )
184
+
185
+ # 也支援 ? 萬用字元(符合單一字元)
186
+ finlab_sentinel.register_preprocess_hook(
187
+ "price:?",
188
+ lambda df: df.round(2)
189
+ )
190
+
191
+ finlab_sentinel.enable()
192
+
193
+ # 使用 finlab
194
+ from finlab import data
195
+ close = data.get('price:收盤價') # 比對時會先 round(2),但回傳原始資料
196
+ ```
197
+
198
+ ### 進階用法
199
+
200
+ ```python
201
+ import finlab_sentinel
202
+
203
+ # 自訂預處理函式
204
+ def normalize_for_comparison(df):
205
+ """標準化 DataFrame 以忽略預期的差異"""
206
+ df = df.copy()
207
+ # 四捨五入數值欄位
208
+ numeric_cols = df.select_dtypes(include=['float64', 'float32']).columns
209
+ df[numeric_cols] = df[numeric_cols].round(4)
210
+ # 排序欄位(忽略欄位順序差異)
211
+ df = df[sorted(df.columns)]
212
+ return df
213
+
214
+ finlab_sentinel.register_preprocess_hook("fundamental_features:*", normalize_for_comparison)
215
+
216
+ # 取消註冊
217
+ finlab_sentinel.unregister_preprocess_hook("price:收盤價")
218
+
219
+ # 清除所有 hooks
220
+ finlab_sentinel.clear_preprocess_hooks()
221
+ ```
222
+
223
+ ### 優先順序
224
+
225
+ 當多個 pattern 都符合時,精確匹配優先於萬用字元匹配:
226
+
227
+ ```python
228
+ finlab_sentinel.register_preprocess_hook("price:*", lambda df: df.round(1))
229
+ finlab_sentinel.register_preprocess_hook("price:收盤價", lambda df: df.round(2))
230
+
231
+ # "price:收盤價" 會使用 round(2)(精確匹配)
232
+ # "price:開盤價" 會使用 round(1)(萬用字元匹配)
233
+ ```
234
+
235
+ ## 自訂通知
236
+
237
+ ```python
238
+ def send_line_notification(report):
239
+ """當檢測到異常時發送 LINE 通知"""
240
+ import requests
241
+ requests.post(
242
+ "https://notify-api.line.me/api/notify",
243
+ headers={"Authorization": f"Bearer {LINE_TOKEN}"},
244
+ data={"message": f"finlab 資料異常: {report.summary}"}
245
+ )
246
+
247
+ # 在 sentinel.toml 中設定
248
+ # [anomaly]
249
+ # callback = "myproject.notifications:send_line_notification"
250
+ ```
251
+
252
+ ## 開發
253
+
254
+ ```bash
255
+ # Clone 專案
256
+ git clone https://github.com/yourusername/finlab-sentinel
257
+ cd finlab-sentinel
258
+
259
+ # 使用 uv 安裝開發依賴
260
+ uv sync --dev
261
+
262
+ # 執行測試
263
+ uv run pytest
264
+
265
+ # 執行 lint
266
+ uv run ruff check src/ tests/
267
+ uv run mypy src/
268
+ ```
269
+
270
+ ## License
271
+
272
+ MIT License