refactor: address high-priority debt and publish to PyPI
- Fix TOCTOU race conditions by wrapping read-modify-write cycles under single-file locks in execution_state, portfolio_service, precheck_state, state_manager, and precheck_service. - Add missing test coverage (96 tests total): - test_review_service.py (15 tests) - test_check_api.py (6 tests) - test_external_gate.py main branches (+10 tests) - test_trade_execution.py new commands (+8 tests) - Unify all agent-consumed JSON messages to English. - Config-ize hardcoded values (volume filter, schema_version) via get_user_config with sensible defaults. - Add 1-hour TTL to exchange cache with force_new override. - Add ruff and mypy to dev dependencies; fix all type errors. - Add __all__ declarations to 11 service modules. - Sync README with new commands, config tuning docs, and PyPI badge. - Publish package as coinhunter==1.0.0 on PyPI with MIT license. - Deprecate coinhunter-cli==1.0.1 with runtime warning. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
from dataclasses import asdict, dataclass
|
||||
@@ -24,6 +25,7 @@ class RuntimePaths:
|
||||
positions_lock: Path
|
||||
executions_lock: Path
|
||||
precheck_state_file: Path
|
||||
precheck_state_lock: Path
|
||||
external_gate_lock: Path
|
||||
logrotate_config: Path
|
||||
logrotate_status: Path
|
||||
@@ -64,6 +66,7 @@ def get_runtime_paths() -> RuntimePaths:
|
||||
positions_lock=root / "positions.lock",
|
||||
executions_lock=root / "executions.lock",
|
||||
precheck_state_file=state_dir / "precheck_state.json",
|
||||
precheck_state_lock=state_dir / "precheck_state.lock",
|
||||
external_gate_lock=state_dir / "external_gate.lock",
|
||||
logrotate_config=root / "logrotate_external_gate.conf",
|
||||
logrotate_status=state_dir / "logrotate_external_gate.status",
|
||||
@@ -105,3 +108,20 @@ def mask_secret(value: str | None, *, tail: int = 4) -> str:
|
||||
if len(value) <= tail:
|
||||
return "*" * len(value)
|
||||
return "*" * max(4, len(value) - tail) + value[-tail:]
|
||||
|
||||
|
||||
def get_user_config(key: str, default=None):
|
||||
"""Read a dotted key from the user config file."""
|
||||
paths = get_runtime_paths()
|
||||
try:
|
||||
config = json.loads(paths.config_file.read_text(encoding="utf-8"))
|
||||
except Exception:
|
||||
return default
|
||||
for part in key.split("."):
|
||||
if isinstance(config, dict):
|
||||
config = config.get(part)
|
||||
if config is None:
|
||||
return default
|
||||
else:
|
||||
return default
|
||||
return config if config is not None else default
|
||||
|
||||
Reference in New Issue
Block a user