Handle missing Docker config file mounts
Browse files- Dockerfile +1 -0
- README.md +2 -0
- services/config.py +24 -6
- test/test_config.py +64 -0
Dockerfile
CHANGED
|
@@ -31,6 +31,7 @@ COPY pyproject.toml uv.lock ./
|
|
| 31 |
RUN uv sync --frozen --no-dev --no-install-project
|
| 32 |
|
| 33 |
COPY main.py ./
|
|
|
|
| 34 |
COPY VERSION ./
|
| 35 |
COPY services ./services
|
| 36 |
COPY --from=web-build /app/web/out ./web_dist
|
|
|
|
| 31 |
RUN uv sync --frozen --no-dev --no-install-project
|
| 32 |
|
| 33 |
COPY main.py ./
|
| 34 |
+
COPY config.example.json ./
|
| 35 |
COPY VERSION ./
|
| 36 |
COPY services ./services
|
| 37 |
COPY --from=web-build /app/web/out ./web_dist
|
README.md
CHANGED
|
@@ -74,6 +74,8 @@ git clone git@github.com:basketikun/chatgpt2api.git
|
|
| 74 |
docker compose up -d
|
| 75 |
```
|
| 76 |
|
|
|
|
|
|
|
| 77 |
## 社区支持
|
| 78 |
学 AI , 上 L 站
|
| 79 |
|
|
|
|
| 74 |
docker compose up -d
|
| 75 |
```
|
| 76 |
|
| 77 |
+
如果之前在宿主机上没有 `config.json` 就直接执行了 `docker compose up -d`,Docker 可能会错误地创建一个 `config.json/` 目录。遇到这种情况请先删除这个目录,再重新创建 `config.json` 文件后启动。
|
| 78 |
+
|
| 79 |
## 社区支持
|
| 80 |
学 AI , 上 L 站
|
| 81 |
|
services/config.py
CHANGED
|
@@ -3,6 +3,7 @@ from __future__ import annotations
|
|
| 3 |
import json
|
| 4 |
import os
|
| 5 |
import shutil
|
|
|
|
| 6 |
from dataclasses import dataclass
|
| 7 |
from pathlib import Path
|
| 8 |
from typing import cast
|
|
@@ -22,6 +23,19 @@ class AppSettings:
|
|
| 22 |
accounts_file: Path
|
| 23 |
refresh_account_interval_minute: int
|
| 24 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
def _load_json_object(path: Path, *, name: str) -> dict[str, object]:
|
| 26 |
text = path.read_text(encoding="utf-8").strip()
|
| 27 |
if not text:
|
|
@@ -36,9 +50,10 @@ def _load_json_object(path: Path, *, name: str) -> dict[str, object]:
|
|
| 36 |
def _ensure_config_file() -> None:
|
| 37 |
if CONFIG_FILE.exists():
|
| 38 |
return
|
| 39 |
-
|
|
|
|
| 40 |
return
|
| 41 |
-
shutil.copyfile(
|
| 42 |
|
| 43 |
|
| 44 |
def _load_settings() -> AppSettings:
|
|
@@ -46,10 +61,13 @@ def _load_settings() -> AppSettings:
|
|
| 46 |
_ensure_config_file()
|
| 47 |
|
| 48 |
raw_config: dict[str, object] = {}
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
|
|
|
|
|
|
|
|
|
| 53 |
|
| 54 |
auth_key = str(os.getenv("CHATGPT2API_AUTH_KEY") or raw_config.get("auth-key") or "").strip()
|
| 55 |
if not auth_key:
|
|
|
|
| 3 |
import json
|
| 4 |
import os
|
| 5 |
import shutil
|
| 6 |
+
import sys
|
| 7 |
from dataclasses import dataclass
|
| 8 |
from pathlib import Path
|
| 9 |
from typing import cast
|
|
|
|
| 23 |
accounts_file: Path
|
| 24 |
refresh_account_interval_minute: int
|
| 25 |
|
| 26 |
+
|
| 27 |
+
def _readable_json_file(path: Path, *, name: str) -> Path | None:
|
| 28 |
+
if not path.exists():
|
| 29 |
+
return None
|
| 30 |
+
if path.is_dir():
|
| 31 |
+
print(
|
| 32 |
+
f"Warning: {name} at '{path}' is a directory, ignoring it and falling back to other configuration sources.",
|
| 33 |
+
file=sys.stderr,
|
| 34 |
+
)
|
| 35 |
+
return None
|
| 36 |
+
return path
|
| 37 |
+
|
| 38 |
+
|
| 39 |
def _load_json_object(path: Path, *, name: str) -> dict[str, object]:
|
| 40 |
text = path.read_text(encoding="utf-8").strip()
|
| 41 |
if not text:
|
|
|
|
| 50 |
def _ensure_config_file() -> None:
|
| 51 |
if CONFIG_FILE.exists():
|
| 52 |
return
|
| 53 |
+
example_file = _readable_json_file(CONFIG_EXAMPLE_FILE, name="config.example.json")
|
| 54 |
+
if example_file is None:
|
| 55 |
return
|
| 56 |
+
shutil.copyfile(example_file, CONFIG_FILE)
|
| 57 |
|
| 58 |
|
| 59 |
def _load_settings() -> AppSettings:
|
|
|
|
| 61 |
_ensure_config_file()
|
| 62 |
|
| 63 |
raw_config: dict[str, object] = {}
|
| 64 |
+
example_file = _readable_json_file(CONFIG_EXAMPLE_FILE, name="config.example.json")
|
| 65 |
+
if example_file is not None:
|
| 66 |
+
raw_config.update(_load_json_object(example_file, name="config.example.json"))
|
| 67 |
+
|
| 68 |
+
config_file = _readable_json_file(CONFIG_FILE, name="config.json")
|
| 69 |
+
if config_file is not None:
|
| 70 |
+
raw_config.update(_load_json_object(config_file, name="config.json"))
|
| 71 |
|
| 72 |
auth_key = str(os.getenv("CHATGPT2API_AUTH_KEY") or raw_config.get("auth-key") or "").strip()
|
| 73 |
if not auth_key:
|
test/test_config.py
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json
|
| 2 |
+
import tempfile
|
| 3 |
+
import unittest
|
| 4 |
+
from pathlib import Path
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
ROOT_DIR = Path(__file__).resolve().parents[1]
|
| 8 |
+
ROOT_CONFIG_FILE = ROOT_DIR / "config.json"
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
class ConfigLoadingTests(unittest.TestCase):
|
| 12 |
+
@classmethod
|
| 13 |
+
def setUpClass(cls) -> None:
|
| 14 |
+
cls._created_root_config = False
|
| 15 |
+
if not ROOT_CONFIG_FILE.exists():
|
| 16 |
+
ROOT_CONFIG_FILE.write_text(json.dumps({"auth-key": "test-auth"}), encoding="utf-8")
|
| 17 |
+
cls._created_root_config = True
|
| 18 |
+
|
| 19 |
+
from services import config as config_module
|
| 20 |
+
|
| 21 |
+
cls.config_module = config_module
|
| 22 |
+
|
| 23 |
+
@classmethod
|
| 24 |
+
def tearDownClass(cls) -> None:
|
| 25 |
+
if cls._created_root_config and ROOT_CONFIG_FILE.exists():
|
| 26 |
+
ROOT_CONFIG_FILE.unlink()
|
| 27 |
+
|
| 28 |
+
def test_load_settings_falls_back_to_example_when_config_path_is_directory(self) -> None:
|
| 29 |
+
with tempfile.TemporaryDirectory() as tmp_dir:
|
| 30 |
+
base_dir = Path(tmp_dir)
|
| 31 |
+
data_dir = base_dir / "data"
|
| 32 |
+
config_dir = base_dir / "config.json"
|
| 33 |
+
example_file = base_dir / "config.example.json"
|
| 34 |
+
|
| 35 |
+
config_dir.mkdir()
|
| 36 |
+
example_file.write_text(
|
| 37 |
+
json.dumps({"auth-key": "example-auth", "refresh_account_interval_minute": 15}),
|
| 38 |
+
encoding="utf-8",
|
| 39 |
+
)
|
| 40 |
+
|
| 41 |
+
module = self.config_module
|
| 42 |
+
old_base_dir = module.BASE_DIR
|
| 43 |
+
old_data_dir = module.DATA_DIR
|
| 44 |
+
old_config_file = module.CONFIG_FILE
|
| 45 |
+
old_config_example_file = module.CONFIG_EXAMPLE_FILE
|
| 46 |
+
try:
|
| 47 |
+
module.BASE_DIR = base_dir
|
| 48 |
+
module.DATA_DIR = data_dir
|
| 49 |
+
module.CONFIG_FILE = config_dir
|
| 50 |
+
module.CONFIG_EXAMPLE_FILE = example_file
|
| 51 |
+
|
| 52 |
+
settings = module._load_settings()
|
| 53 |
+
|
| 54 |
+
self.assertEqual(settings.auth_key, "example-auth")
|
| 55 |
+
self.assertEqual(settings.refresh_account_interval_minute, 15)
|
| 56 |
+
finally:
|
| 57 |
+
module.BASE_DIR = old_base_dir
|
| 58 |
+
module.DATA_DIR = old_data_dir
|
| 59 |
+
module.CONFIG_FILE = old_config_file
|
| 60 |
+
module.CONFIG_EXAMPLE_FILE = old_config_example_file
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
if __name__ == "__main__":
|
| 64 |
+
unittest.main()
|