feat: add config module with INI-based configuration management
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
70
src/jianda_proxy/config.py
Normal file
70
src/jianda_proxy/config.py
Normal file
@@ -0,0 +1,70 @@
|
||||
import os
|
||||
import configparser
|
||||
from pathlib import Path
|
||||
|
||||
SECTION = "proxy"
|
||||
DEFAULTS = {
|
||||
"remote_domain": "git.zz.com",
|
||||
"upstream_host": "62.234.191.215",
|
||||
"upstream_port": "3000",
|
||||
"listen_port": "13000",
|
||||
"listen_host": "127.0.0.1",
|
||||
}
|
||||
|
||||
|
||||
def get_config_dir():
|
||||
if os.name == "nt":
|
||||
base = os.environ.get("APPDATA", os.path.expanduser("~"))
|
||||
else:
|
||||
base = os.environ.get("XDG_CONFIG_HOME", os.path.expanduser("~/.config"))
|
||||
return os.path.join(base, "jianda-proxy")
|
||||
|
||||
|
||||
def _ensure_dir(path):
|
||||
os.makedirs(path, exist_ok=True)
|
||||
|
||||
|
||||
def get_config_path():
|
||||
return os.path.join(get_config_dir(), "config.ini")
|
||||
|
||||
|
||||
def get_pid_path():
|
||||
return os.path.join(get_config_dir(), "daemon.pid")
|
||||
|
||||
|
||||
def get_log_path():
|
||||
return os.path.join(get_config_dir(), "proxy.log")
|
||||
|
||||
|
||||
def load_config():
|
||||
path = get_config_path()
|
||||
_ensure_dir(get_config_dir())
|
||||
parser = configparser.ConfigParser()
|
||||
if not os.path.exists(path):
|
||||
parser[SECTION] = dict(DEFAULTS)
|
||||
with open(path, "w") as f:
|
||||
parser.write(f)
|
||||
else:
|
||||
parser.read(path)
|
||||
if SECTION not in parser:
|
||||
parser[SECTION] = dict(DEFAULTS)
|
||||
return parser
|
||||
|
||||
|
||||
def set_config_value(key, value):
|
||||
if key not in DEFAULTS:
|
||||
raise ValueError(f"Unknown config key: {key}. Valid keys: {', '.join(DEFAULTS)}")
|
||||
parser = load_config()
|
||||
parser[SECTION][key] = value
|
||||
with open(get_config_path(), "w") as f:
|
||||
parser.write(f)
|
||||
|
||||
|
||||
def get_config_value(key):
|
||||
parser = load_config()
|
||||
return parser.get(SECTION, key)
|
||||
|
||||
|
||||
def list_config():
|
||||
parser = load_config()
|
||||
return dict(parser[SECTION])
|
||||
82
tests/test_config.py
Normal file
82
tests/test_config.py
Normal file
@@ -0,0 +1,82 @@
|
||||
import os
|
||||
import tempfile
|
||||
import pytest
|
||||
from configparser import ConfigParser
|
||||
from jianda_proxy.config import (
|
||||
get_config_dir,
|
||||
get_config_path,
|
||||
get_pid_path,
|
||||
get_log_path,
|
||||
load_config,
|
||||
set_config_value,
|
||||
get_config_value,
|
||||
list_config,
|
||||
DEFAULTS,
|
||||
)
|
||||
|
||||
FIXTURES = {
|
||||
"remote_domain": "git.zz.com",
|
||||
"upstream_host": "62.234.191.215",
|
||||
"upstream_port": "3000",
|
||||
"listen_port": "13000",
|
||||
"listen_host": "127.0.0.1",
|
||||
}
|
||||
|
||||
|
||||
class TestConfigDir:
|
||||
def test_returns_path_with_app_name(self, tmp_path, monkeypatch):
|
||||
monkeypatch.setenv("HOME", str(tmp_path))
|
||||
result = get_config_dir()
|
||||
assert "jianda-proxy" in result
|
||||
|
||||
def test_windows_uses_appdata(self, tmp_path, monkeypatch):
|
||||
monkeypatch.setenv("APPDATA", str(tmp_path))
|
||||
monkeypatch.delenv("HOME", raising=False)
|
||||
monkeypatch.delenv("XDG_CONFIG_HOME", raising=False)
|
||||
result = get_config_dir()
|
||||
assert "jianda-proxy" in result
|
||||
|
||||
|
||||
class TestConfigCRUD:
|
||||
def test_load_creates_default_config(self, tmp_path, monkeypatch):
|
||||
monkeypatch.setattr("jianda_proxy.config.get_config_dir", lambda: str(tmp_path))
|
||||
config = load_config()
|
||||
for key, val in FIXTURES.items():
|
||||
assert config.get("proxy", key) == val
|
||||
|
||||
def test_load_creates_config_file(self, tmp_path, monkeypatch):
|
||||
monkeypatch.setattr("jianda_proxy.config.get_config_dir", lambda: str(tmp_path))
|
||||
load_config()
|
||||
assert (tmp_path / "config.ini").exists()
|
||||
|
||||
def test_set_and_get_value(self, tmp_path, monkeypatch):
|
||||
monkeypatch.setattr("jianda_proxy.config.get_config_dir", lambda: str(tmp_path))
|
||||
set_config_value("listen_port", "8080")
|
||||
assert get_config_value("listen_port") == "8080"
|
||||
|
||||
def test_set_unknown_key_raises(self, tmp_path, monkeypatch):
|
||||
monkeypatch.setattr("jianda_proxy.config.get_config_dir", lambda: str(tmp_path))
|
||||
with pytest.raises(ValueError, match="Unknown config key"):
|
||||
set_config_value("nonexistent_key", "value")
|
||||
|
||||
def test_list_config(self, tmp_path, monkeypatch):
|
||||
monkeypatch.setattr("jianda_proxy.config.get_config_dir", lambda: str(tmp_path))
|
||||
load_config()
|
||||
result = list_config()
|
||||
assert isinstance(result, dict)
|
||||
for key, val in FIXTURES.items():
|
||||
assert result[key] == val
|
||||
|
||||
|
||||
class TestPaths:
|
||||
def test_config_path(self, tmp_path, monkeypatch):
|
||||
monkeypatch.setattr("jianda_proxy.config.get_config_dir", lambda: str(tmp_path))
|
||||
assert get_config_path().endswith("config.ini")
|
||||
|
||||
def test_pid_path(self, tmp_path, monkeypatch):
|
||||
monkeypatch.setattr("jianda_proxy.config.get_config_dir", lambda: str(tmp_path))
|
||||
assert get_pid_path().endswith("daemon.pid")
|
||||
|
||||
def test_log_path(self, tmp_path, monkeypatch):
|
||||
monkeypatch.setattr("jianda_proxy.config.get_config_dir", lambda: str(tmp_path))
|
||||
assert get_log_path().endswith("proxy.log")
|
||||
Reference in New Issue
Block a user