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.
Files changed (72) hide show
  1. emquant_cli-0.1.0/.gitignore +29 -0
  2. emquant_cli-0.1.0/ARCHITECHTURE.md +47 -0
  3. emquant_cli-0.1.0/LICENSE +21 -0
  4. emquant_cli-0.1.0/PKG-INFO +91 -0
  5. emquant_cli-0.1.0/README.md +75 -0
  6. emquant_cli-0.1.0/pyproject.toml +73 -0
  7. emquant_cli-0.1.0/src/emq/__init__.py +1 -0
  8. emquant_cli-0.1.0/src/emq/__main__.py +4 -0
  9. emquant_cli-0.1.0/src/emq/cli.py +39 -0
  10. emquant_cli-0.1.0/src/emq/commands/__init__.py +0 -0
  11. emquant_cli-0.1.0/src/emq/commands/_common.py +124 -0
  12. emquant_cli-0.1.0/src/emq/commands/auth.py +57 -0
  13. emquant_cli-0.1.0/src/emq/commands/market.py +63 -0
  14. emquant_cli-0.1.0/src/emq/commands/portfolio.py +234 -0
  15. emquant_cli-0.1.0/src/emq/commands/quota.py +57 -0
  16. emquant_cli-0.1.0/src/emq/commands/raw.py +188 -0
  17. emquant_cli-0.1.0/src/emq/core/__init__.py +0 -0
  18. emquant_cli-0.1.0/src/emq/core/config.py +27 -0
  19. emquant_cli-0.1.0/src/emq/core/emquant_loader.py +65 -0
  20. emquant_cli-0.1.0/src/emq/core/errors.py +17 -0
  21. emquant_cli-0.1.0/src/emq/core/logging.py +12 -0
  22. emquant_cli-0.1.0/src/emq/core/normalize.py +73 -0
  23. emquant_cli-0.1.0/src/emq/core/output.py +125 -0
  24. emquant_cli-0.1.0/src/emq/core/platform_support.py +63 -0
  25. emquant_cli-0.1.0/src/emq/core/session.py +292 -0
  26. emquant_cli-0.1.0/src/emq/core/state.py +64 -0
  27. emquant_cli-0.1.0/src/emq/types.py +19 -0
  28. emquant_cli-0.1.0/src/emq/vendor/__init__.py +0 -0
  29. emquant_cli-0.1.0/src/emq/vendor/emquantapi/__init__.py +0 -0
  30. emquant_cli-0.1.0/src/emq/vendor/emquantapi/python3/EmQuantAPI.py +2092 -0
  31. emquant_cli-0.1.0/src/emq/vendor/emquantapi/python3/libs/linux/x64/ServerList.json.e +1 -0
  32. emquant_cli-0.1.0/src/emq/vendor/emquantapi/python3/libs/linux/x64/image/EMApp.ico +0 -0
  33. emquant_cli-0.1.0/src/emq/vendor/emquantapi/python3/libs/linux/x64/image/Tips_error.png +0 -0
  34. emquant_cli-0.1.0/src/emq/vendor/emquantapi/python3/libs/linux/x64/image/edit_bg.png +0 -0
  35. emquant_cli-0.1.0/src/emq/vendor/emquantapi/python3/libs/linux/x64/image/tab1_bg.png +0 -0
  36. emquant_cli-0.1.0/src/emq/vendor/emquantapi/python3/libs/linux/x64/image/tab2_bg.png +0 -0
  37. emquant_cli-0.1.0/src/emq/vendor/emquantapi/python3/libs/linux/x64/image/tab3_bg.png +0 -0
  38. emquant_cli-0.1.0/src/emq/vendor/emquantapi/python3/libs/linux/x64/libEMQuantAPIx64.so +0 -0
  39. emquant_cli-0.1.0/src/emq/vendor/emquantapi/python3/libs/linux/x64/loginactivator +0 -0
  40. emquant_cli-0.1.0/src/emq/vendor/emquantapi/python3/libs/linux/x64/loginactivator_ubuntu +0 -0
  41. emquant_cli-0.1.0/src/emq/vendor/emquantapi/python3/libs/linux/x86/ServerList.json.e +1 -0
  42. emquant_cli-0.1.0/src/emq/vendor/emquantapi/python3/libs/linux/x86/image/EMApp.ico +0 -0
  43. emquant_cli-0.1.0/src/emq/vendor/emquantapi/python3/libs/linux/x86/image/Tips_error.png +0 -0
  44. emquant_cli-0.1.0/src/emq/vendor/emquantapi/python3/libs/linux/x86/image/edit_bg.png +0 -0
  45. emquant_cli-0.1.0/src/emq/vendor/emquantapi/python3/libs/linux/x86/image/tab1_bg.png +0 -0
  46. emquant_cli-0.1.0/src/emq/vendor/emquantapi/python3/libs/linux/x86/image/tab2_bg.png +0 -0
  47. emquant_cli-0.1.0/src/emq/vendor/emquantapi/python3/libs/linux/x86/image/tab3_bg.png +0 -0
  48. emquant_cli-0.1.0/src/emq/vendor/emquantapi/python3/libs/linux/x86/libEMQuantAPI.so +0 -0
  49. emquant_cli-0.1.0/src/emq/vendor/emquantapi/python3/libs/linux/x86/loginactivator +0 -0
  50. emquant_cli-0.1.0/src/emq/vendor/emquantapi/python3/libs/linux/x86/loginactivator_ubuntu +0 -0
  51. emquant_cli-0.1.0/src/emq/vendor/emquantapi/python3/libs/mac/ChoiceToHQ.xml +349 -0
  52. emquant_cli-0.1.0/src/emq/vendor/emquantapi/python3/libs/mac/ServerList.json.e +1 -0
  53. emquant_cli-0.1.0/src/emq/vendor/emquantapi/python3/libs/mac/ServerSelect.txt +1 -0
  54. emquant_cli-0.1.0/src/emq/vendor/emquantapi/python3/libs/mac/bjse_code_conversion.txt +249 -0
  55. emquant_cli-0.1.0/src/emq/vendor/emquantapi/python3/libs/mac/filesumpc.ini +19 -0
  56. emquant_cli-0.1.0/src/emq/vendor/emquantapi/python3/libs/mac/image/EMApp.ico +0 -0
  57. emquant_cli-0.1.0/src/emq/vendor/emquantapi/python3/libs/mac/image/Tips_error.png +0 -0
  58. emquant_cli-0.1.0/src/emq/vendor/emquantapi/python3/libs/mac/image/edit_bg.png +0 -0
  59. emquant_cli-0.1.0/src/emq/vendor/emquantapi/python3/libs/mac/image/tab1_bg.png +0 -0
  60. emquant_cli-0.1.0/src/emq/vendor/emquantapi/python3/libs/mac/image/tab2_bg.png +0 -0
  61. emquant_cli-0.1.0/src/emq/vendor/emquantapi/python3/libs/mac/image/tab3_bg.png +0 -0
  62. emquant_cli-0.1.0/src/emq/vendor/emquantapi/python3/libs/mac/libEMQuantAPIx64.dylib +0 -0
  63. emquant_cli-0.1.0/src/emq/vendor/emquantapi/python3/libs/mac/loginactivator_mac +0 -0
  64. emquant_cli-0.1.0/src/emq/vendor/emquantapi/python3/libs/mac/precentflag.dat +1 -0
  65. emquant_cli-0.1.0/src/emq/vendor/emquantapi/python3/libs/windows/EmQuantAPI.dll +0 -0
  66. emquant_cli-0.1.0/src/emq/vendor/emquantapi/python3/libs/windows/EmQuantAPI_x64.dll +0 -0
  67. emquant_cli-0.1.0/src/emq/vendor/emquantapi/python3/libs/windows/LoginActivator.exe +0 -0
  68. emquant_cli-0.1.0/src/emq/vendor/emquantapi/python3/libs/windows/ServerList.json.e +1 -0
  69. emquant_cli-0.1.0/tests/test_cli.py +239 -0
  70. emquant_cli-0.1.0/tests/test_platform_support.py +28 -0
  71. emquant_cli-0.1.0/tests/test_session.py +187 -0
  72. 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,4 @@
1
+ from emq.cli import main
2
+
3
+ if __name__ == "__main__":
4
+ main()
@@ -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)