Repository navigation
Expand file tree
/
Copy pathpyproject.toml
More file actions
267 lines (252 loc) · 12 KB
/
Copy pathpyproject.toml
File metadata and controls
267 lines (252 loc) · 12 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
[build-system]
requires = ["setuptools>=77", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "cnequity"
version = "0.16.0"
description = "Open, self-hosted financial data infrastructure for China markets, starting with A-shares"
# Short PyPI page (absolute links only). GitHub keeps README.md / README.en.md.
readme = "README.pypi.md"
requires-python = ">=3.10"
license = "Apache-2.0"
license-files = ["LICENSE*", "NOTICE"]
authors = [{ name = "CNEquity Contributors" }]
keywords = [
"a-share",
"china-stocks",
"china-equity",
"data-lake",
"mcp",
"ai-agent",
"duckdb",
"polars",
"parquet",
"quantitative-finance",
"market-data",
]
classifiers = [
"Development Status :: 3 - Alpha",
"Intended Audience :: Financial and Insurance Industry",
"Operating System :: MacOS",
"Operating System :: Microsoft :: Windows",
"Operating System :: POSIX :: Linux",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"Topic :: Office/Business :: Financial :: Investment",
]
# One install, no extras: `pip install cnequity` brings every runtime source
# the daily pipeline and the backfill paths need. Splitting these out again would
# bring back the failure mode that motivated folding them in — a source enabled
# in config with no package behind it, degrading silently on a scheduled run.
dependencies = [
"click>=8.1",
"duckdb>=1.0",
# The 0.25 floor existed only so mootdx (httpx>=0.25,<0.26) could co-resolve.
# mootdx is gone — the TDX client is vendored — so nothing caps httpx now and
# the floor just marks the oldest release with the Client() options we use.
"httpx>=0.25",
# Chrome TLS impersonation for push2his (plain httpx often gets Empty reply overseas).
"curl_cffi>=0.7",
"polars>=1.0",
# Option pricing (domain/option_pricing.py) vectorises Black-76 and BAW over
# a session's strikes. Already present through pandas; declared because the
# lake now imports it directly.
"numpy>=1.26",
"pyarrow>=15.0",
# CNINFO issuer notices are the primary source for exact historical cash
# payment dates. pypdf extracts the text whose symbol, ex-date, pretax cash
# amount and payment date are all cross-checked before a repair is accepted.
"pypdf>=5.0",
"PyYAML>=6.0",
# No AkShare. Its two call sites wrapped endpoints already queried directly —
# the ST board (push2 clist) and the PMI / 货币供应量 datacenter reports — so it
# bought a parsing layer, 14 transitive packages and the mini-racer collision
# rather than a second source. See issue #3.
# Baostock: valuation / ST / delisted-bar history for `cne init` and backfills.
"baostock>=0.8",
# SnowNLP: on-demand stock_news sentiment ([sentiment] use_snownlp).
"snownlp>=0.12",
# Shenwan / CNI publish constituent history as XLS and XLSX only; polars cannot
# read either, so pandas is here as a parser, not as a dataframe layer.
"pandas>=2.0",
"openpyxl>=3.0",
"xlrd>=2.0",
# stdlib tomllib is 3.11+; config loader falls back to tomli on 3.10.
"tomli>=2; python_version < '3.11'",
# `cne serve` — the read-only lake dashboard. FastAPI earns its ~8 packages on
# the response models and the OpenAPI page they generate: /api/docs is the
# API reference, so it cannot drift from the handlers, and the same models are
# what keep the CLI's JSON and the dashboard's JSON one contract instead of
# two. Starlette alone would route just as well and document nothing.
"fastapi>=0.110",
"uvicorn>=0.27",
]
# No [project.optional-dependencies]. Everything is a hard dependency.
# `pip install "cnequity[tdx]"` from an older doc still installs correctly —
# pip prints "does not provide the extra 'tdx'" and carries on, uv is silent —
# so declaring empty extras would only advertise things that no longer exist.
# Contributor tooling lives in a PEP 735 dependency group, not an extra: it is
# never part of what a user installs. `pip install --group dev` (pip >= 25.1) or
# `uv sync`.
[dependency-groups]
dev = [
"pytest>=8.0",
"pytest-cov>=5.0",
"pytest-timeout>=2.3",
# Pinned to a minor line: CI runs `ruff format --check`, and the formatter's
# line-wrapping changes between minors, so an unpinned `ruff>=0.4` turns any
# upstream release into a red build on code nobody touched.
"ruff>=0.15,<0.16",
# Runs the same two checks before the commit instead of after the push.
# `pre-commit install` once per clone; see .pre-commit-config.yaml.
"pre-commit>=3.5",
"pytest-randomly>=5.0.0",
]
[project.urls]
Homepage = "https://github.com/rootSunc/CNEquity"
Documentation = "https://rootsunc.github.io/CNEquity/"
Source = "https://github.com/rootSunc/CNEquity"
Changelog = "https://github.com/rootSunc/CNEquity/blob/main/CHANGELOG.md"
Citation = "https://github.com/rootSunc/CNEquity/blob/main/CITATION.cff"
Issues = "https://github.com/rootSunc/CNEquity/issues"
[project.scripts]
cne = "cnequity.cli.main:cli"
# Same CLI under the package name, so `uvx cnequity mcp ...` works — the
# command the MCP Registry builds from server.json.
cnequity = "cnequity.cli.main:cli"
[tool.setuptools.packages.find]
where = ["src"]
[tool.setuptools.data-files]
"share/cnequity" = ["sources/SOURCES.yml"]
# Versioned dataset contracts are release governance artifacts, not runtime
# Python modules. Ship the contract alongside the installed policy so an
# offline consumer can retain the exact contract used by a release.
"share/cnequity/contracts" = ["contracts/v0.10.0.json", "contracts/v0.11.0.json", "contracts/v0.11.1.json", "contracts/v0.12.0.json", "contracts/v0.13.0.json", "contracts/v0.14.0.json", "contracts/v0.14.1.json", "contracts/v0.15.0.json", "contracts/v0.16.0.json"]
[tool.setuptools.package-data]
cnequity = [
"py.typed",
"adapters/calendar/seeds/*.csv",
# Required by BSE symbol migration and full-lake audits on installed wheels.
"adapters/eastmoney/seeds/*.json",
"adapters/ths/seeds/*.json",
# hexin-v token generator for 同花顺 data pages (vendored from AKShare, MIT);
# run under Deno with no permissions — see adapters/ths/hexin.py.
"adapters/ths/js/*.js",
# Public DigiCert intermediate that swsresearch.com fails to send. Without
# it in the wheel every Shenwan fetch fails TLS verification on a fresh
# install — see adapters/sw/industry_history.py.
"adapters/sw/*.pem",
"config/templates/*.toml",
# The dashboard page. Self-contained on purpose (no CDN, no build step): a
# lake often runs on an offline box or behind a proxy that would turn an
# external asset into a page that does not load.
"serve/static/*.html",
"serve/static/*.js",
"serve/static/*.css",
# MIT requires the notice to travel with every copy of the vendored code.
"adapters/tdx_protocol/_wire/LICENSE.tdxpy",
]
[tool.ruff]
line-length = 100
target-version = "py310"
src = ["src", "tests"]
# Vendored upstream code (tdxpy, MIT), file by file rather than by directory.
# There is no upstream to re-sync from — tdxpy's last release was 2024 and its
# being unmaintained is why the tree is vendored at all — so the exclusion is
# not about keeping a diff cheap. It is about not owning a style: these files
# are still byte-close to tdxpy, and reformatting them would rewrite code
# nobody here has read line by line.
#
# Deliberately NOT listed, because they are ours and only live here:
# _wire/__init__.py — TdxWireClient, page caps, heartbeat
# parser/std/get_transaction_data.py — three upstream defects fixed
# parser/std/get_history_transaction_data.py — ditto, and raw prices
# A new file under _wire is ours by default and gets linted; add it below only
# if it is a fresh port of upstream code.
extend-exclude = [
"src/cnequity/adapters/tdx_protocol/_wire/constants.py",
"src/cnequity/adapters/tdx_protocol/_wire/exceptions.py",
"src/cnequity/adapters/tdx_protocol/_wire/heartbeat.py",
"src/cnequity/adapters/tdx_protocol/_wire/helper.py",
"src/cnequity/adapters/tdx_protocol/_wire/logger.py",
"src/cnequity/adapters/tdx_protocol/_wire/socket_client.py",
"src/cnequity/adapters/tdx_protocol/_wire/parser/base.py",
"src/cnequity/adapters/tdx_protocol/_wire/parser/raw_parser.py",
"src/cnequity/adapters/tdx_protocol/_wire/parser/setup_commands.py",
"src/cnequity/adapters/tdx_protocol/_wire/parser/std/get_index_bars.py",
"src/cnequity/adapters/tdx_protocol/_wire/parser/std/get_security_bars.py",
"src/cnequity/adapters/tdx_protocol/_wire/parser/std/get_security_count.py",
"src/cnequity/adapters/tdx_protocol/_wire/parser/std/get_security_list.py",
"src/cnequity/adapters/tdx_protocol/_wire/parser/std/get_xdxr_info.py",
]
# extend-exclude alone only applies while ruff walks a directory. Anything that
# passes filenames explicitly — pre-commit, an editor-on-save action, a manual
# `ruff format <path>` — bypasses it and would reformat the vendored tree.
force-exclude = true
[tool.ruff.lint]
select = ["E", "F", "I", "UP", "B", "W"]
ignore = ["E501"]
[tool.ruff.lint.per-file-ignores]
# Scripts insert repo src onto sys.path before importing the package.
"scripts/**/*.py" = ["E402"]
[tool.ruff.lint.isort]
known-first-party = ["cnequity"]
[tool.pytest.ini_options]
testpaths = ["tests"]
# Network probes (EastMoney schema contracts, etc.) run only with `-m network`.
# Random order is opt-in (`-p randomly`): CI runs one shuffled pass to catch
# tests that only pass after another test has set shared state.
addopts = "-ra -m 'not network' -p no:randomly"
# Fail loud on a hung test instead of blocking the whole suite and leaving an
# orphaned process behind. The full suite runs in ~10s; 120s is generous slack.
# `thread` method dumps a traceback pinpointing where the hang occurred.
timeout = 120
timeout_method = "thread"
markers = [
"integration: end-to-end tests that exercise the full pipeline (may be slower)",
"network: tests that require outbound network access",
]
# A warning in the run output should mean something went unexpectedly wrong.
# Two kinds were drowning that signal: ~30 tests exercise the deprecated
# `universe="all_a"` alias *on purpose* — that is what they are for, so the
# warning they raise is the expected result, not a defect — and starlette's
# TestClient announces an httpx2 preference on import. Silence both by name,
# never broadly: anything else still surfaces, and an unexpected
# DeprecationWarning is still visible.
filterwarnings = [
"ignore:universe='all_a' is a deprecated compatibility alias:DeprecationWarning",
# Starlette raises its own warning class, not DeprecationWarning, and does so
# at import time — match on the message alone.
"ignore:Using `httpx` with `starlette.testclient` is deprecated",
]
[tool.coverage.run]
source = ["cnequity"]
branch = true
# Vendored upstream (tdxpy, MIT). Same boundary as ruff extend-exclude, and the
# same three exceptions: `_wire/__init__.py` and the two transaction parsers are
# first-party code that happens to sit in the vendored tree, so they are
# measured like everything else. Omitting them hid the one place in this package
# where a parsing regression corrupts every later row of a page.
omit = [
"*/adapters/tdx_protocol/_wire/constants.py",
"*/adapters/tdx_protocol/_wire/exceptions.py",
"*/adapters/tdx_protocol/_wire/heartbeat.py",
"*/adapters/tdx_protocol/_wire/helper.py",
"*/adapters/tdx_protocol/_wire/logger.py",
"*/adapters/tdx_protocol/_wire/socket_client.py",
"*/adapters/tdx_protocol/_wire/parser/base.py",
"*/adapters/tdx_protocol/_wire/parser/raw_parser.py",
"*/adapters/tdx_protocol/_wire/parser/setup_commands.py",
"*/adapters/tdx_protocol/_wire/parser/std/get_index_bars.py",
"*/adapters/tdx_protocol/_wire/parser/std/get_security_bars.py",
"*/adapters/tdx_protocol/_wire/parser/std/get_security_count.py",
"*/adapters/tdx_protocol/_wire/parser/std/get_security_list.py",
"*/adapters/tdx_protocol/_wire/parser/std/get_xdxr_info.py",
]
[tool.coverage.report]
show_missing = true
skip_empty = true