emquant-cli 0.1.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.
- emquant_cli-0.1.0/.gitignore +29 -0
- emquant_cli-0.1.0/ARCHITECHTURE.md +47 -0
- emquant_cli-0.1.0/LICENSE +21 -0
- emquant_cli-0.1.0/PKG-INFO +91 -0
- emquant_cli-0.1.0/README.md +75 -0
- emquant_cli-0.1.0/pyproject.toml +73 -0
- emquant_cli-0.1.0/src/emq/__init__.py +1 -0
- emquant_cli-0.1.0/src/emq/__main__.py +4 -0
- emquant_cli-0.1.0/src/emq/cli.py +39 -0
- emquant_cli-0.1.0/src/emq/commands/__init__.py +0 -0
- emquant_cli-0.1.0/src/emq/commands/_common.py +124 -0
- emquant_cli-0.1.0/src/emq/commands/auth.py +57 -0
- emquant_cli-0.1.0/src/emq/commands/market.py +63 -0
- emquant_cli-0.1.0/src/emq/commands/portfolio.py +234 -0
- emquant_cli-0.1.0/src/emq/commands/quota.py +57 -0
- emquant_cli-0.1.0/src/emq/commands/raw.py +188 -0
- emquant_cli-0.1.0/src/emq/core/__init__.py +0 -0
- emquant_cli-0.1.0/src/emq/core/config.py +27 -0
- emquant_cli-0.1.0/src/emq/core/emquant_loader.py +65 -0
- emquant_cli-0.1.0/src/emq/core/errors.py +17 -0
- emquant_cli-0.1.0/src/emq/core/logging.py +12 -0
- emquant_cli-0.1.0/src/emq/core/normalize.py +73 -0
- emquant_cli-0.1.0/src/emq/core/output.py +125 -0
- emquant_cli-0.1.0/src/emq/core/platform_support.py +63 -0
- emquant_cli-0.1.0/src/emq/core/session.py +292 -0
- emquant_cli-0.1.0/src/emq/core/state.py +64 -0
- emquant_cli-0.1.0/src/emq/types.py +19 -0
- emquant_cli-0.1.0/src/emq/vendor/__init__.py +0 -0
- emquant_cli-0.1.0/src/emq/vendor/emquantapi/__init__.py +0 -0
- emquant_cli-0.1.0/src/emq/vendor/emquantapi/python3/EmQuantAPI.py +2092 -0
- emquant_cli-0.1.0/src/emq/vendor/emquantapi/python3/libs/linux/x64/ServerList.json.e +1 -0
- emquant_cli-0.1.0/src/emq/vendor/emquantapi/python3/libs/linux/x64/image/EMApp.ico +0 -0
- emquant_cli-0.1.0/src/emq/vendor/emquantapi/python3/libs/linux/x64/image/Tips_error.png +0 -0
- emquant_cli-0.1.0/src/emq/vendor/emquantapi/python3/libs/linux/x64/image/edit_bg.png +0 -0
- emquant_cli-0.1.0/src/emq/vendor/emquantapi/python3/libs/linux/x64/image/tab1_bg.png +0 -0
- emquant_cli-0.1.0/src/emq/vendor/emquantapi/python3/libs/linux/x64/image/tab2_bg.png +0 -0
- emquant_cli-0.1.0/src/emq/vendor/emquantapi/python3/libs/linux/x64/image/tab3_bg.png +0 -0
- emquant_cli-0.1.0/src/emq/vendor/emquantapi/python3/libs/linux/x64/libEMQuantAPIx64.so +0 -0
- emquant_cli-0.1.0/src/emq/vendor/emquantapi/python3/libs/linux/x64/loginactivator +0 -0
- emquant_cli-0.1.0/src/emq/vendor/emquantapi/python3/libs/linux/x64/loginactivator_ubuntu +0 -0
- emquant_cli-0.1.0/src/emq/vendor/emquantapi/python3/libs/linux/x86/ServerList.json.e +1 -0
- emquant_cli-0.1.0/src/emq/vendor/emquantapi/python3/libs/linux/x86/image/EMApp.ico +0 -0
- emquant_cli-0.1.0/src/emq/vendor/emquantapi/python3/libs/linux/x86/image/Tips_error.png +0 -0
- emquant_cli-0.1.0/src/emq/vendor/emquantapi/python3/libs/linux/x86/image/edit_bg.png +0 -0
- emquant_cli-0.1.0/src/emq/vendor/emquantapi/python3/libs/linux/x86/image/tab1_bg.png +0 -0
- emquant_cli-0.1.0/src/emq/vendor/emquantapi/python3/libs/linux/x86/image/tab2_bg.png +0 -0
- emquant_cli-0.1.0/src/emq/vendor/emquantapi/python3/libs/linux/x86/image/tab3_bg.png +0 -0
- emquant_cli-0.1.0/src/emq/vendor/emquantapi/python3/libs/linux/x86/libEMQuantAPI.so +0 -0
- emquant_cli-0.1.0/src/emq/vendor/emquantapi/python3/libs/linux/x86/loginactivator +0 -0
- emquant_cli-0.1.0/src/emq/vendor/emquantapi/python3/libs/linux/x86/loginactivator_ubuntu +0 -0
- emquant_cli-0.1.0/src/emq/vendor/emquantapi/python3/libs/mac/ChoiceToHQ.xml +349 -0
- emquant_cli-0.1.0/src/emq/vendor/emquantapi/python3/libs/mac/ServerList.json.e +1 -0
- emquant_cli-0.1.0/src/emq/vendor/emquantapi/python3/libs/mac/ServerSelect.txt +1 -0
- emquant_cli-0.1.0/src/emq/vendor/emquantapi/python3/libs/mac/bjse_code_conversion.txt +249 -0
- emquant_cli-0.1.0/src/emq/vendor/emquantapi/python3/libs/mac/filesumpc.ini +19 -0
- emquant_cli-0.1.0/src/emq/vendor/emquantapi/python3/libs/mac/image/EMApp.ico +0 -0
- emquant_cli-0.1.0/src/emq/vendor/emquantapi/python3/libs/mac/image/Tips_error.png +0 -0
- emquant_cli-0.1.0/src/emq/vendor/emquantapi/python3/libs/mac/image/edit_bg.png +0 -0
- emquant_cli-0.1.0/src/emq/vendor/emquantapi/python3/libs/mac/image/tab1_bg.png +0 -0
- emquant_cli-0.1.0/src/emq/vendor/emquantapi/python3/libs/mac/image/tab2_bg.png +0 -0
- emquant_cli-0.1.0/src/emq/vendor/emquantapi/python3/libs/mac/image/tab3_bg.png +0 -0
- emquant_cli-0.1.0/src/emq/vendor/emquantapi/python3/libs/mac/libEMQuantAPIx64.dylib +0 -0
- emquant_cli-0.1.0/src/emq/vendor/emquantapi/python3/libs/mac/loginactivator_mac +0 -0
- emquant_cli-0.1.0/src/emq/vendor/emquantapi/python3/libs/mac/precentflag.dat +1 -0
- emquant_cli-0.1.0/src/emq/vendor/emquantapi/python3/libs/windows/EmQuantAPI.dll +0 -0
- emquant_cli-0.1.0/src/emq/vendor/emquantapi/python3/libs/windows/EmQuantAPI_x64.dll +0 -0
- emquant_cli-0.1.0/src/emq/vendor/emquantapi/python3/libs/windows/LoginActivator.exe +0 -0
- emquant_cli-0.1.0/src/emq/vendor/emquantapi/python3/libs/windows/ServerList.json.e +1 -0
- emquant_cli-0.1.0/tests/test_cli.py +239 -0
- emquant_cli-0.1.0/tests/test_platform_support.py +28 -0
- emquant_cli-0.1.0/tests/test_session.py +187 -0
- emquant_cli-0.1.0/tests/test_validate_commits.py +54 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# Virtual environments
|
|
7
|
+
.venv/
|
|
8
|
+
venv/
|
|
9
|
+
|
|
10
|
+
# Distribution / packaging
|
|
11
|
+
build/
|
|
12
|
+
dist/
|
|
13
|
+
*.egg-info/
|
|
14
|
+
|
|
15
|
+
# Tool caches
|
|
16
|
+
.mypy_cache/
|
|
17
|
+
.pytest_cache/
|
|
18
|
+
.ruff_cache/
|
|
19
|
+
.coverage
|
|
20
|
+
coverage.xml
|
|
21
|
+
|
|
22
|
+
# OS / editor
|
|
23
|
+
.DS_Store
|
|
24
|
+
.idea/
|
|
25
|
+
.vscode/
|
|
26
|
+
|
|
27
|
+
# EmQuant API local login artifact
|
|
28
|
+
src/emq/vendor/emquantapi/python3/libs/mac/logininfo.log
|
|
29
|
+
src/emq/vendor/emquantapi/python3/libs/mac/config.e
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# EMQ CLI Architecture Map
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
The CLI is structured by business domains and a shared core layer:
|
|
6
|
+
|
|
7
|
+
- `src/emq/cli.py`: root command and global options.
|
|
8
|
+
- `src/emq/commands/*`: domain command handlers (`auth`, `market`, `portfolio`, `quota`, `raw`).
|
|
9
|
+
- `src/emq/core/*`: common runtime services.
|
|
10
|
+
- `src/emq/vendor/emquantapi/python3`: vendored EmQuant SDK and native libraries.
|
|
11
|
+
|
|
12
|
+
## Core Modules
|
|
13
|
+
|
|
14
|
+
- `config.py`: constants, env names, vendor paths.
|
|
15
|
+
- `emquant_loader.py`: dynamic import + native library path patching.
|
|
16
|
+
- `session.py`: login lifecycle and auto-login.
|
|
17
|
+
- `state.py`: local credential state persistence.
|
|
18
|
+
- `normalize.py`: convert `EmQuantData` into row-based data.
|
|
19
|
+
- `output.py`: envelope creation + `json/table/csv` rendering.
|
|
20
|
+
- `errors.py`: domain exceptions and exit codes.
|
|
21
|
+
- `logging.py`: CLI logging initialization.
|
|
22
|
+
|
|
23
|
+
## Command Flow
|
|
24
|
+
|
|
25
|
+
1. Root callback parses global options (`--output`, logging, auto-login).
|
|
26
|
+
2. Domain command validates inputs and invokes core session.
|
|
27
|
+
3. Loader returns EmQuant client from vendored SDK.
|
|
28
|
+
4. SDK result is normalized into row records.
|
|
29
|
+
5. Output layer renders the unified envelope in selected format.
|
|
30
|
+
|
|
31
|
+
## Session Model
|
|
32
|
+
|
|
33
|
+
1. `auth login` logs in and stores credentials in `~/.emq/state.json`.
|
|
34
|
+
2. Business commands call `ensure_login()` to auto-login if needed.
|
|
35
|
+
3. `auth logout` calls `stop()` and clears local state.
|
|
36
|
+
4. `auth status` supports local-only and remote probe modes.
|
|
37
|
+
|
|
38
|
+
## Data and Error Model
|
|
39
|
+
|
|
40
|
+
All commands emit:
|
|
41
|
+
|
|
42
|
+
- `success`: boolean
|
|
43
|
+
- `error`: `{code,message,source}` or `null`
|
|
44
|
+
- `meta`: command metadata + row count
|
|
45
|
+
- `data`: row records
|
|
46
|
+
|
|
47
|
+
Errors from SDK (`ErrorCode != 0`) are mapped to `source=emquant`.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 emq-cli contributors
|
|
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,91 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: emquant-cli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Command-line client for EmQuantAPI with bundled runtime libraries.
|
|
5
|
+
Author: emquant-cli maintainers
|
|
6
|
+
License: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Requires-Python: >=3.10
|
|
14
|
+
Requires-Dist: typer<1.0.0,>=0.16.0
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
|
|
17
|
+
# emquant-cli
|
|
18
|
+
|
|
19
|
+
`emquant-cli` provides the `emquant` command-line client for EmQuantAPI with bundled runtime
|
|
20
|
+
libraries.
|
|
21
|
+
|
|
22
|
+
## Requirements
|
|
23
|
+
|
|
24
|
+
- Python 3.10+
|
|
25
|
+
|
|
26
|
+
## Platform Support
|
|
27
|
+
|
|
28
|
+
- macOS: `x86_64`, `arm64` (universal wheel)
|
|
29
|
+
- Linux: `x86_64` only
|
|
30
|
+
- Windows: `amd64` only
|
|
31
|
+
|
|
32
|
+
Linux `arm64/aarch64` is not supported by the vendored EmQuant Linux SDK at this time.
|
|
33
|
+
|
|
34
|
+
## Quick Start
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pip install emquant-cli
|
|
38
|
+
emquant --help
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Login once (credentials from flags or env):
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
export EMQ_USER='your_user'
|
|
45
|
+
export EMQ_PASS='your_pass'
|
|
46
|
+
emquant auth login
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Sample exec query:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
emquant exec csd 000001.SZ CLOSE --start 2025-01-01 --end 2025-12-31 --output csv
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Command Domains
|
|
56
|
+
|
|
57
|
+
- `auth`: `login`, `logout`, `status`
|
|
58
|
+
- `quota`: `usage`
|
|
59
|
+
- `exec`: `css`, `csd`
|
|
60
|
+
|
|
61
|
+
The CLI is intentionally small and focused on authentication, quota checks, and direct
|
|
62
|
+
EmQuant execution commands.
|
|
63
|
+
|
|
64
|
+
## Output
|
|
65
|
+
|
|
66
|
+
Every command uses a unified envelope and supports:
|
|
67
|
+
|
|
68
|
+
- `--output json` (default)
|
|
69
|
+
- `--output csv`
|
|
70
|
+
|
|
71
|
+
`--output` can be placed either before the domain command (global) or at the end of leaf commands (override).
|
|
72
|
+
If both are provided, the leaf command `--output` takes precedence.
|
|
73
|
+
|
|
74
|
+
## Credential Persistence
|
|
75
|
+
|
|
76
|
+
- `auth login` saves credentials to `~/.emq/state.json` (plain text, initial version).
|
|
77
|
+
- Business commands auto-login from saved state or environment variables.
|
|
78
|
+
- `auth logout` clears local state and calls SDK logout.
|
|
79
|
+
|
|
80
|
+
## Development
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
uv run ruff check .
|
|
84
|
+
uv run mypy src
|
|
85
|
+
uv run pytest
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Notes
|
|
89
|
+
|
|
90
|
+
- The vendored SDK lives in `src/emq/vendor/emquantapi/python3`.
|
|
91
|
+
- Root `EMQuantAPI_Python` should not be kept after migration.
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# emquant-cli
|
|
2
|
+
|
|
3
|
+
`emquant-cli` provides the `emquant` command-line client for EmQuantAPI with bundled runtime
|
|
4
|
+
libraries.
|
|
5
|
+
|
|
6
|
+
## Requirements
|
|
7
|
+
|
|
8
|
+
- Python 3.10+
|
|
9
|
+
|
|
10
|
+
## Platform Support
|
|
11
|
+
|
|
12
|
+
- macOS: `x86_64`, `arm64` (universal wheel)
|
|
13
|
+
- Linux: `x86_64` only
|
|
14
|
+
- Windows: `amd64` only
|
|
15
|
+
|
|
16
|
+
Linux `arm64/aarch64` is not supported by the vendored EmQuant Linux SDK at this time.
|
|
17
|
+
|
|
18
|
+
## Quick Start
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pip install emquant-cli
|
|
22
|
+
emquant --help
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Login once (credentials from flags or env):
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
export EMQ_USER='your_user'
|
|
29
|
+
export EMQ_PASS='your_pass'
|
|
30
|
+
emquant auth login
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Sample exec query:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
emquant exec csd 000001.SZ CLOSE --start 2025-01-01 --end 2025-12-31 --output csv
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Command Domains
|
|
40
|
+
|
|
41
|
+
- `auth`: `login`, `logout`, `status`
|
|
42
|
+
- `quota`: `usage`
|
|
43
|
+
- `exec`: `css`, `csd`
|
|
44
|
+
|
|
45
|
+
The CLI is intentionally small and focused on authentication, quota checks, and direct
|
|
46
|
+
EmQuant execution commands.
|
|
47
|
+
|
|
48
|
+
## Output
|
|
49
|
+
|
|
50
|
+
Every command uses a unified envelope and supports:
|
|
51
|
+
|
|
52
|
+
- `--output json` (default)
|
|
53
|
+
- `--output csv`
|
|
54
|
+
|
|
55
|
+
`--output` can be placed either before the domain command (global) or at the end of leaf commands (override).
|
|
56
|
+
If both are provided, the leaf command `--output` takes precedence.
|
|
57
|
+
|
|
58
|
+
## Credential Persistence
|
|
59
|
+
|
|
60
|
+
- `auth login` saves credentials to `~/.emq/state.json` (plain text, initial version).
|
|
61
|
+
- Business commands auto-login from saved state or environment variables.
|
|
62
|
+
- `auth logout` clears local state and calls SDK logout.
|
|
63
|
+
|
|
64
|
+
## Development
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
uv run ruff check .
|
|
68
|
+
uv run mypy src
|
|
69
|
+
uv run pytest
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Notes
|
|
73
|
+
|
|
74
|
+
- The vendored SDK lives in `src/emq/vendor/emquantapi/python3`.
|
|
75
|
+
- Root `EMQuantAPI_Python` should not be kept after migration.
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling>=1.24.0"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "emquant-cli"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Command-line client for EmQuantAPI with bundled runtime libraries."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "emquant-cli maintainers" }]
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Programming Language :: Python :: 3",
|
|
15
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
16
|
+
"Programming Language :: Python :: 3.10",
|
|
17
|
+
"Programming Language :: Python :: 3.11",
|
|
18
|
+
"Programming Language :: Python :: 3.12",
|
|
19
|
+
]
|
|
20
|
+
dependencies = [
|
|
21
|
+
"typer>=0.16.0,<1.0.0",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
[project.scripts]
|
|
25
|
+
emquant = "emq.cli:main"
|
|
26
|
+
|
|
27
|
+
[dependency-groups]
|
|
28
|
+
dev = [
|
|
29
|
+
"coverage[toml]>=7.8.0,<8.0.0",
|
|
30
|
+
"mypy>=1.15.0,<2.0.0",
|
|
31
|
+
"pytest>=8.3.0,<9.0.0",
|
|
32
|
+
"ruff>=0.11.0,<0.12.0",
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
[tool.ruff]
|
|
36
|
+
line-length = 100
|
|
37
|
+
target-version = "py310"
|
|
38
|
+
extend-exclude = ["EMQuantAPI_Python", "src/emq/vendor"]
|
|
39
|
+
|
|
40
|
+
[tool.ruff.lint]
|
|
41
|
+
select = ["E", "F", "I", "B", "UP"]
|
|
42
|
+
|
|
43
|
+
[tool.mypy]
|
|
44
|
+
python_version = "3.10"
|
|
45
|
+
mypy_path = ["src"]
|
|
46
|
+
warn_return_any = true
|
|
47
|
+
warn_unused_configs = true
|
|
48
|
+
disallow_untyped_defs = true
|
|
49
|
+
no_implicit_optional = true
|
|
50
|
+
check_untyped_defs = true
|
|
51
|
+
strict_equality = true
|
|
52
|
+
exclude = "(^src/emq/vendor/|^EMQuantAPI_Python/)"
|
|
53
|
+
|
|
54
|
+
[tool.pytest.ini_options]
|
|
55
|
+
addopts = "-ra"
|
|
56
|
+
testpaths = ["tests"]
|
|
57
|
+
|
|
58
|
+
[tool.hatch.build.targets.wheel]
|
|
59
|
+
packages = ["src/emq"]
|
|
60
|
+
[tool.hatch.build]
|
|
61
|
+
include = [
|
|
62
|
+
"src/emq/**",
|
|
63
|
+
"README.md",
|
|
64
|
+
"ARCHITECHTURE.md",
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
[tool.hatch.build.targets.sdist]
|
|
68
|
+
include = [
|
|
69
|
+
"src/emq",
|
|
70
|
+
"tests",
|
|
71
|
+
"README.md",
|
|
72
|
+
"ARCHITECHTURE.md",
|
|
73
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""emq package."""
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import typer
|
|
4
|
+
|
|
5
|
+
from emq.commands._common import normalize_output
|
|
6
|
+
from emq.commands.auth import app as auth_app
|
|
7
|
+
from emq.commands.quota import app as quota_app
|
|
8
|
+
from emq.commands.raw import public_app as exec_app
|
|
9
|
+
from emq.core.logging import setup_logging
|
|
10
|
+
|
|
11
|
+
app = typer.Typer(
|
|
12
|
+
name="emquant",
|
|
13
|
+
help="EmQuant command line interface.",
|
|
14
|
+
add_completion=False,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
app.add_typer(auth_app, name="auth")
|
|
18
|
+
app.add_typer(quota_app, name="quota")
|
|
19
|
+
app.add_typer(exec_app, name="exec")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@app.callback()
|
|
23
|
+
def callback(
|
|
24
|
+
ctx: typer.Context,
|
|
25
|
+
output: str = typer.Option("json", "--output", help="Output format: json|csv."),
|
|
26
|
+
log_level: str = typer.Option("INFO", "--log-level", help="Log level."),
|
|
27
|
+
log_file: str | None = typer.Option(None, "--log-file", help="Optional log file path."),
|
|
28
|
+
no_auto_login: bool = typer.Option(False, "--no-auto-login", help="Disable automatic login."),
|
|
29
|
+
) -> None:
|
|
30
|
+
setup_logging(log_level, log_file)
|
|
31
|
+
fmt = normalize_output(output)
|
|
32
|
+
|
|
33
|
+
ctx.ensure_object(dict)
|
|
34
|
+
ctx.obj["output"] = fmt
|
|
35
|
+
ctx.obj["no_auto_login"] = no_auto_login
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def main() -> None:
|
|
39
|
+
app()
|
|
File without changes
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Callable
|
|
4
|
+
from ctypes import ArgumentError
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
|
|
9
|
+
from emq.core.errors import EmqCliError
|
|
10
|
+
from emq.core.normalize import normalize_emquant_data
|
|
11
|
+
from emq.core.output import build_envelope, emit
|
|
12
|
+
from emq.core.platform_support import ensure_sdk_runtime_supported
|
|
13
|
+
from emq.core.session import stop_active_session
|
|
14
|
+
from emq.types import ErrorInfo
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def normalize_output(value: str) -> str:
|
|
18
|
+
fmt = value.lower().strip()
|
|
19
|
+
if fmt not in {"json", "csv"}:
|
|
20
|
+
raise typer.BadParameter("--output must be one of: json, csv")
|
|
21
|
+
return fmt
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def apply_output_override(ctx: typer.Context, output_override: str | None) -> None:
|
|
25
|
+
if output_override is None:
|
|
26
|
+
return
|
|
27
|
+
ctx.ensure_object(dict)
|
|
28
|
+
ctx.obj["output"] = normalize_output(output_override)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def get_global_output(ctx: typer.Context) -> str:
|
|
32
|
+
obj = ctx.obj or {}
|
|
33
|
+
return str(obj.get("output", "json"))
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def get_no_auto_login(ctx: typer.Context) -> bool:
|
|
37
|
+
obj = ctx.obj or {}
|
|
38
|
+
return bool(obj.get("no_auto_login", False))
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def emit_success(
|
|
42
|
+
ctx: typer.Context,
|
|
43
|
+
*,
|
|
44
|
+
command: str,
|
|
45
|
+
rows: list[dict[str, Any]],
|
|
46
|
+
meta: dict[str, Any] | None = None,
|
|
47
|
+
) -> None:
|
|
48
|
+
output = get_global_output(ctx)
|
|
49
|
+
envelope = build_envelope(command=command, data=rows, output=output, meta=meta)
|
|
50
|
+
emit(envelope, output)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def emit_error(ctx: typer.Context, *, command: str, error: EmqCliError) -> None:
|
|
54
|
+
output = get_global_output(ctx)
|
|
55
|
+
envelope = build_envelope(
|
|
56
|
+
command=command,
|
|
57
|
+
data=[],
|
|
58
|
+
output=output,
|
|
59
|
+
error=ErrorInfo(code=error.code, message=error.message, source=error.source),
|
|
60
|
+
)
|
|
61
|
+
emit(envelope, output)
|
|
62
|
+
raise typer.Exit(code=error.exit_code)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def execute_sdk_command(
|
|
66
|
+
ctx: typer.Context,
|
|
67
|
+
*,
|
|
68
|
+
command: str,
|
|
69
|
+
fn: Callable[[], Any],
|
|
70
|
+
) -> None:
|
|
71
|
+
try:
|
|
72
|
+
ensure_sdk_runtime_supported()
|
|
73
|
+
result = fn()
|
|
74
|
+
if hasattr(result, "ErrorCode") and int(result.ErrorCode) != 0:
|
|
75
|
+
raise EmqCliError(
|
|
76
|
+
str(getattr(result, "ErrorMsg", "EmQuant API error")),
|
|
77
|
+
code=int(result.ErrorCode),
|
|
78
|
+
source="emquant",
|
|
79
|
+
exit_code=3,
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
if hasattr(result, "Data"):
|
|
83
|
+
rows, meta = normalize_emquant_data(result)
|
|
84
|
+
emit_success(ctx, command=command, rows=rows, meta=meta)
|
|
85
|
+
elif isinstance(result, dict):
|
|
86
|
+
emit_success(ctx, command=command, rows=[result], meta={"row_count": 1})
|
|
87
|
+
elif isinstance(result, list):
|
|
88
|
+
normalized_rows = [{"value": item} for item in result]
|
|
89
|
+
emit_success(
|
|
90
|
+
ctx,
|
|
91
|
+
command=command,
|
|
92
|
+
rows=normalized_rows,
|
|
93
|
+
meta={"row_count": len(result)},
|
|
94
|
+
)
|
|
95
|
+
else:
|
|
96
|
+
emit_success(ctx, command=command, rows=[{"value": result}], meta={"row_count": 1})
|
|
97
|
+
except (OSError, ImportError) as exc:
|
|
98
|
+
emit_error(
|
|
99
|
+
ctx,
|
|
100
|
+
command=command,
|
|
101
|
+
error=EmqCliError(
|
|
102
|
+
f"Failed to load EmQuant native runtime: {exc}",
|
|
103
|
+
code="EMQUANT_NATIVE_LOAD_ERROR",
|
|
104
|
+
source="emquant",
|
|
105
|
+
exit_code=3,
|
|
106
|
+
),
|
|
107
|
+
)
|
|
108
|
+
except (ArgumentError, TypeError, ValueError) as exc:
|
|
109
|
+
emit_error(
|
|
110
|
+
ctx,
|
|
111
|
+
command=command,
|
|
112
|
+
error=EmqCliError(
|
|
113
|
+
f"Invalid arguments for EmQuant command: {exc}",
|
|
114
|
+
code="EMQUANT_ARGUMENT_ERROR",
|
|
115
|
+
source="emquant",
|
|
116
|
+
exit_code=2,
|
|
117
|
+
),
|
|
118
|
+
)
|
|
119
|
+
except EmqCliError as exc:
|
|
120
|
+
emit_error(ctx, command=command, error=exc)
|
|
121
|
+
finally:
|
|
122
|
+
# The CLI is one-shot. Always stop the SDK session before process exit so
|
|
123
|
+
# background threads do not linger and crash the interpreter on Linux.
|
|
124
|
+
stop_active_session()
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import typer
|
|
4
|
+
|
|
5
|
+
from emq.commands._common import apply_output_override, execute_sdk_command
|
|
6
|
+
from emq.core.session import login, logout, status
|
|
7
|
+
|
|
8
|
+
app = typer.Typer(help="Authentication commands.")
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@app.command("login")
|
|
12
|
+
def login_cmd(
|
|
13
|
+
ctx: typer.Context,
|
|
14
|
+
user: str | None = typer.Option(None, "--user", help="EMQ username."),
|
|
15
|
+
password: str | None = typer.Option(None, "--password", help="EMQ password."),
|
|
16
|
+
force_login: bool = typer.Option(
|
|
17
|
+
True, "--force-login/--no-force-login", help="Set ForceLogin."
|
|
18
|
+
),
|
|
19
|
+
save: bool = typer.Option(True, "--save/--no-save", help="Save credentials to local state."),
|
|
20
|
+
output: str | None = typer.Option(
|
|
21
|
+
None, "--output", help="Output format override: json|csv."
|
|
22
|
+
),
|
|
23
|
+
) -> None:
|
|
24
|
+
apply_output_override(ctx, output)
|
|
25
|
+
execute_sdk_command(
|
|
26
|
+
ctx,
|
|
27
|
+
command="auth.login",
|
|
28
|
+
fn=lambda: login(user=user, password=password, force_login=force_login, save=save),
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@app.command("logout")
|
|
33
|
+
def logout_cmd(
|
|
34
|
+
ctx: typer.Context,
|
|
35
|
+
output: str | None = typer.Option(
|
|
36
|
+
None, "--output", help="Output format override: json|csv."
|
|
37
|
+
),
|
|
38
|
+
) -> None:
|
|
39
|
+
apply_output_override(ctx, output)
|
|
40
|
+
execute_sdk_command(ctx, command="auth.logout", fn=logout)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@app.command("status")
|
|
44
|
+
def status_cmd(
|
|
45
|
+
ctx: typer.Context,
|
|
46
|
+
check: bool = typer.Option(False, "--check", help="Probe remote API status."),
|
|
47
|
+
output: str | None = typer.Option(
|
|
48
|
+
None, "--output", help="Output format override: json|csv."
|
|
49
|
+
),
|
|
50
|
+
) -> None:
|
|
51
|
+
apply_output_override(ctx, output)
|
|
52
|
+
no_auto_login = bool((ctx.obj or {}).get("no_auto_login", False))
|
|
53
|
+
execute_sdk_command(
|
|
54
|
+
ctx,
|
|
55
|
+
command="auth.status",
|
|
56
|
+
fn=lambda: status(check=check, no_auto_login=no_auto_login),
|
|
57
|
+
)
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
|
|
7
|
+
from emq.commands._common import apply_output_override, execute_sdk_command, get_no_auto_login
|
|
8
|
+
from emq.core.emquant_loader import get_emquant_client
|
|
9
|
+
from emq.core.session import ensure_login
|
|
10
|
+
|
|
11
|
+
app = typer.Typer(help="Market data commands.")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@app.command("snapshot")
|
|
15
|
+
def snapshot(
|
|
16
|
+
ctx: typer.Context,
|
|
17
|
+
codes: str = typer.Argument(..., help="Codes, comma-separated."),
|
|
18
|
+
indicators: str = typer.Argument(..., help="Indicators, comma-separated."),
|
|
19
|
+
options: str = typer.Option("", "--options", help="Raw EmQuant options string."),
|
|
20
|
+
output: str | None = typer.Option(
|
|
21
|
+
None, "--output", help="Output format override: json|table|csv."
|
|
22
|
+
),
|
|
23
|
+
) -> None:
|
|
24
|
+
apply_output_override(ctx, output)
|
|
25
|
+
execute_sdk_command(
|
|
26
|
+
ctx,
|
|
27
|
+
command="market.snapshot",
|
|
28
|
+
fn=lambda: _snapshot(codes, indicators, options, get_no_auto_login(ctx)),
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@app.command("series")
|
|
33
|
+
def series(
|
|
34
|
+
ctx: typer.Context,
|
|
35
|
+
codes: str = typer.Argument(..., help="Codes, comma-separated."),
|
|
36
|
+
indicators: str = typer.Argument(..., help="Indicators, comma-separated."),
|
|
37
|
+
start: str = typer.Option(..., "--start", help="Start date YYYY-MM-DD."),
|
|
38
|
+
end: str = typer.Option(..., "--end", help="End date YYYY-MM-DD."),
|
|
39
|
+
options: str = typer.Option("", "--options", help="Raw EmQuant options string."),
|
|
40
|
+
output: str | None = typer.Option(
|
|
41
|
+
None, "--output", help="Output format override: json|table|csv."
|
|
42
|
+
),
|
|
43
|
+
) -> None:
|
|
44
|
+
apply_output_override(ctx, output)
|
|
45
|
+
execute_sdk_command(
|
|
46
|
+
ctx,
|
|
47
|
+
command="market.series",
|
|
48
|
+
fn=lambda: _series(codes, indicators, start, end, options, get_no_auto_login(ctx)),
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _snapshot(codes: str, indicators: str, options: str, no_auto_login: bool) -> Any:
|
|
53
|
+
ensure_login(no_auto_login=no_auto_login)
|
|
54
|
+
c = get_emquant_client()
|
|
55
|
+
return c.css(codes, indicators, options)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _series(
|
|
59
|
+
codes: str, indicators: str, start: str, end: str, options: str, no_auto_login: bool
|
|
60
|
+
) -> Any:
|
|
61
|
+
ensure_login(no_auto_login=no_auto_login)
|
|
62
|
+
c = get_emquant_client()
|
|
63
|
+
return c.csd(codes, indicators, start, end, options)
|