{"record":{"id":"1580d34cd76b1ce4","repo":"ZhuLinsen/daily_stock_analysis","slug":"tushare-api-token","errorCode":null,"errorMessage":"Tushare API 未初始化，请检查 Token 配置","messagePattern":"Tushare API 未初始化，请检查 Token 配置","errorType":"exception","errorClass":"DataFetchError","httpStatus":null,"severity":"error","filePath":"data_provider/tushare_fetcher.py","lineNumber":291,"sourceCode":"            logger.warning(\n                f\"Tushare 达到速率限制 ({self._call_count}/{self.rate_limit_per_minute} 次/分钟)，\"\n                f\"等待 {sleep_time:.1f} 秒...\"\n            )\n            \n            time.sleep(sleep_time)\n            \n            # 重置计数器\n            self._minute_start = time.time()\n            self._call_count = 0\n        \n        # 增加调用计数\n        self._call_count += 1\n        logger.debug(f\"Tushare 当前分钟调用次数: {self._call_count}/{self.rate_limit_per_minute}\")\n\n    def _call_api_with_rate_limit(self, method_name: str, **kwargs) -> pd.DataFrame:\n        \"\"\"统一通过速率限制包装 Tushare API 调用。\"\"\"\n        if self._api is None:\n            raise DataFetchError(\"Tushare API 未初始化，请检查 Token 配置\")\n\n        self._check_rate_limit()\n        method = getattr(self._api, method_name)\n        return method(**kwargs)\n\n    def _get_china_now(self) -> datetime:\n        \"\"\"返回上海时区当前时间，方便测试覆盖跨日刷新逻辑。\"\"\"\n        return datetime.now(ZoneInfo(\"Asia/Shanghai\"))\n\n    def _get_trade_dates(self, end_date: Optional[str] = None) -> List[str]:\n        \"\"\"按自然日刷新交易日历缓存，避免服务跨日后继续复用旧日历。\"\"\"\n        if self._api is None:\n            return []\n\n        china_now = self._get_china_now()\n        requested_end_date = end_date or china_now.strftime(\"%Y%m%d\")\n\n        if self.date_list is not None and self._date_list_end == requested_end_date:","sourceCodeStart":273,"sourceCodeEnd":309,"githubUrl":"https://github.com/ZhuLinsen/daily_stock_analysis/blob/5159bd72e8373d215492dff122acc9d389e219c9/data_provider/tushare_fetcher.py#L273-L309","documentation":"DataFetchError raised by TushareFetcher._call_api_with_rate_limit when self._api is None — the Tushare client was never initialized, which happens when the token is missing/empty so the pro API client could not be constructed. It fails before the rate-limit bookkeeping and any remote call.","triggerScenarios":"Calling any Tushare data method (daily, trade_cal, ...) via _call_api_with_rate_limit when TUSHARE_TOKEN is unset/blank or initialization was skipped/failed during fetcher construction.","commonSituations":"Missing TUSHARE_TOKEN in .env for a new deployment; token defined only in local shell not in Docker/CI; fetcher instantiated before env is loaded; empty-string token passing a truthiness check elsewhere.","solutions":["Set a valid TUSHARE_TOKEN in the runtime environment (and keep .env.example in sync).","Gate TushareFetcher registration on token presence so unavailable providers are never selected.","Catch DataFetchError at the manager and fall back to Akshare when Tushare is not configured."],"exampleFix":"# before\ndf = tushare_fetcher.get_stock_data(\"600519\", start, end)  # DataFetchError: not initialized\n\n# after\n# .env: TUSHARE_TOKEN=...\nimport os\nif os.getenv(\"TUSHARE_TOKEN\"):\n    df = tushare_fetcher.get_stock_data(\"600519\", start, end)\nelse:\n    df = akshare_fetcher.get_stock_data(\"600519\", start, end)","handlingStrategy":"validation","validationCode":"import os\n\ntushare_ready = bool(os.getenv(\"TUSHARE_TOKEN\", \"\").strip()) and tushare_fetcher._api is not None\nif not tushare_ready:\n    df = akshare_fetcher.get_stock_data(code, start, end)","typeGuard":null,"tryCatchPattern":"try:\n    df = tushare_fetcher.get_stock_data(code, start, end)\nexcept DataFetchError as e:\n    if \"未初始化\" in str(e):\n        df = akshare_fetcher.get_stock_data(code, start, end)\n    else:\n        raise","preventionTips":["Set TUSHARE_TOKEN wherever the app runs (local, CI, Docker) — not just one shell.","Only register TushareFetcher when the token is present and initialization succeeds.","Surface provider init failures at startup logs."],"tags":["tushare","credentials","token","configuration"],"backgroundTag":null,"analyzedSha":"5159bd72e8373d215492dff122acc9d389e219c9","analyzedAt":"2026-08-15T01:59:36.292Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}