mirror of
https://github.com/tiennm99/DocsGPT.git
synced 2026-10-13 20:14:16 +00:00
feat: installed package keeps its data in ~/.docsgpt/server
Outside a checkout the data home was the working directory, so running `docsgpt api` from another folder silently used different settings and data. It is now ~/.docsgpt/server (/opt/docsgpt for root on Linux); DOCSGPT_HOME and a checkout still take precedence. The API and worker commands create the home and point out a .env left in the working directory.
This commit is contained in:
1 parent
95fd4bdefe
commit
ac06527a8d
4 files changed
+106
-11
No files matched your search
+17
-3
@@ -19,10 +19,24 @@ DEFAULT_PORT = 7091
|
||||
|
||||
|
||||
def _announce_home() -> None:
|
||||
"""Say where runtime data and the env file come from; the API and the worker must agree."""
|
||||
from docsgpt.core.paths import env_file, home_dir
|
||||
"""Create the data home and say where data and the env file come from; the API and the worker must agree."""
|
||||
from pathlib import Path
|
||||
|
||||
print(f"docsgpt: data home {home_dir()} (env file {env_file()})", file=sys.stderr)
|
||||
from docsgpt.core import paths
|
||||
|
||||
home = paths.home_dir()
|
||||
home.mkdir(parents=True, exist_ok=True)
|
||||
env = paths.env_file()
|
||||
print(f"docsgpt: data home {home} (env file {env})", file=sys.stderr)
|
||||
# Up to 0.20 an installed package used the working directory as its home.
|
||||
chosen = os.environ.get(paths.HOME_ENV) or os.environ.get(paths.ENV_FILE_ENV) or paths.checkout_root()
|
||||
stray = Path.cwd() / ".env"
|
||||
if not chosen and stray.is_file() and stray.resolve() != env.resolve():
|
||||
print(
|
||||
f"docsgpt: {stray} is not used; settings come from {env}. "
|
||||
f"Move the file there, or set DOCSGPT_HOME={Path.cwd()} to keep using this directory.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
|
||||
|
||||
def _gunicorn_options(host: str, port: int, workers: int) -> dict:
|
||||
|
||||
+16
-6
@@ -5,15 +5,18 @@ ships with it (prompts, model catalogs, migrations). A checkout, when the
|
||||
package is imported from one, is the directory holding ``pyproject.toml``.
|
||||
The data home is where runtime data lives: ``.env``, ``inputs``, ``indexes``.
|
||||
|
||||
The home is ``DOCSGPT_HOME`` when set, else the checkout, else the current
|
||||
directory. That keeps a source checkout and the Docker image (which runs from
|
||||
``/app`` with the package beside it) behaving as before, and gives a
|
||||
``pip install docsgpt`` user a home that is not ``site-packages``.
|
||||
The home is ``DOCSGPT_HOME`` when set, else the checkout, else the default
|
||||
home (``~/.docsgpt/server``, or ``/opt/docsgpt`` for root on Linux). That keeps
|
||||
a source checkout and the Docker image (which pins ``DOCSGPT_HOME=/app``)
|
||||
behaving as before, and gives an installed package one home no matter which
|
||||
directory the command runs from. ``docsgpt up`` keeps its stack in the same
|
||||
place.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
HOME_ENV = "DOCSGPT_HOME"
|
||||
@@ -31,12 +34,19 @@ def checkout_root() -> Path | None:
|
||||
return root if (root / "pyproject.toml").is_file() else None
|
||||
|
||||
|
||||
def default_home() -> Path:
|
||||
"""The home outside a checkout: ``/opt/docsgpt`` for root on Linux, else ``~/.docsgpt/server``."""
|
||||
if sys.platform.startswith("linux") and hasattr(os, "geteuid") and os.geteuid() == 0:
|
||||
return Path("/opt/docsgpt")
|
||||
return Path.home() / ".docsgpt" / "server"
|
||||
|
||||
|
||||
def home_dir() -> Path:
|
||||
"""Directory for runtime data: ``DOCSGPT_HOME``, else the checkout, else cwd."""
|
||||
"""Directory for runtime data: ``DOCSGPT_HOME``, else the checkout, else the default home."""
|
||||
configured = os.environ.get(HOME_ENV)
|
||||
if configured:
|
||||
return Path(configured).expanduser().resolve()
|
||||
return checkout_root() or Path.cwd()
|
||||
return checkout_root() or default_home()
|
||||
|
||||
|
||||
def env_file() -> Path:
|
||||
|
||||
@@ -20,11 +20,31 @@ class TestHomeDir:
|
||||
monkeypatch.setenv(paths.HOME_ENV, str(tmp_path))
|
||||
assert paths.home_dir() == tmp_path.resolve()
|
||||
|
||||
def test_an_installed_package_falls_back_to_cwd(self, monkeypatch, tmp_path):
|
||||
def test_an_installed_package_uses_the_default_home_not_cwd(self, monkeypatch, tmp_path):
|
||||
"""`docsgpt api` from any directory finds the same .env and data."""
|
||||
monkeypatch.delenv(paths.HOME_ENV, raising=False)
|
||||
monkeypatch.setattr(paths, "checkout_root", lambda: None)
|
||||
monkeypatch.setattr(paths, "default_home", lambda: tmp_path / "home")
|
||||
monkeypatch.chdir(tmp_path)
|
||||
assert paths.home_dir() == Path.cwd()
|
||||
assert paths.home_dir() == tmp_path / "home"
|
||||
|
||||
|
||||
class TestDefaultHome:
|
||||
def test_a_user_gets_a_folder_in_their_home(self, monkeypatch, tmp_path):
|
||||
monkeypatch.setattr(paths.sys, "platform", "darwin")
|
||||
monkeypatch.setattr(paths.Path, "home", classmethod(lambda cls: tmp_path))
|
||||
assert paths.default_home() == tmp_path / ".docsgpt" / "server"
|
||||
|
||||
def test_root_on_linux_gets_opt(self, monkeypatch):
|
||||
monkeypatch.setattr(paths.sys, "platform", "linux")
|
||||
monkeypatch.setattr(paths.os, "geteuid", lambda: 0, raising=False)
|
||||
assert paths.default_home() == Path("/opt/docsgpt")
|
||||
|
||||
def test_a_normal_user_on_linux_stays_in_their_home(self, monkeypatch, tmp_path):
|
||||
monkeypatch.setattr(paths.sys, "platform", "linux")
|
||||
monkeypatch.setattr(paths.os, "geteuid", lambda: 1000, raising=False)
|
||||
monkeypatch.setattr(paths.Path, "home", classmethod(lambda cls: tmp_path))
|
||||
assert paths.default_home() == tmp_path / ".docsgpt" / "server"
|
||||
|
||||
|
||||
class TestEnvFile:
|
||||
|
||||
@@ -34,6 +34,57 @@ class TestTopLevel:
|
||||
subprocess.run([sys.executable, "-c", code], cwd=Path(__file__).resolve().parents[1], check=True)
|
||||
|
||||
|
||||
class TestHome:
|
||||
@staticmethod
|
||||
def _installed(monkeypatch, tmp_path):
|
||||
"""An installed package (no checkout) with the default home under tmp_path."""
|
||||
from docsgpt.core import paths
|
||||
|
||||
monkeypatch.delenv(paths.HOME_ENV, raising=False)
|
||||
monkeypatch.delenv(paths.ENV_FILE_ENV, raising=False)
|
||||
monkeypatch.setattr(paths, "checkout_root", lambda: None)
|
||||
monkeypatch.setattr(paths, "default_home", lambda: tmp_path / "home")
|
||||
return tmp_path / "home"
|
||||
|
||||
def test_the_home_is_created_and_announced(self, monkeypatch, tmp_path, capsys):
|
||||
home = self._installed(monkeypatch, tmp_path)
|
||||
monkeypatch.chdir(tmp_path)
|
||||
cli._announce_home()
|
||||
assert home.is_dir()
|
||||
assert f"data home {home}" in capsys.readouterr().err
|
||||
|
||||
def test_an_env_file_left_in_the_working_directory_is_pointed_out(self, monkeypatch, tmp_path, capsys):
|
||||
"""Up to 0.20 an installed package read .env from the working directory."""
|
||||
home = self._installed(monkeypatch, tmp_path)
|
||||
work = tmp_path / "work"
|
||||
work.mkdir()
|
||||
(work / ".env").write_text("LLM_PROVIDER=openai\n")
|
||||
monkeypatch.chdir(work)
|
||||
cli._announce_home()
|
||||
err = capsys.readouterr().err
|
||||
assert f"{work / '.env'} is not used" in err
|
||||
assert f"DOCSGPT_HOME={work}" in err
|
||||
assert str(home) in err
|
||||
|
||||
def test_no_warning_when_the_home_is_chosen_explicitly(self, monkeypatch, tmp_path, capsys):
|
||||
from docsgpt.core import paths
|
||||
|
||||
self._installed(monkeypatch, tmp_path)
|
||||
(tmp_path / ".env").write_text("LLM_PROVIDER=openai\n")
|
||||
monkeypatch.setenv(paths.HOME_ENV, str(tmp_path / "elsewhere"))
|
||||
monkeypatch.chdir(tmp_path)
|
||||
cli._announce_home()
|
||||
assert "is not used" not in capsys.readouterr().err
|
||||
|
||||
def test_no_warning_when_the_working_directory_is_the_home(self, monkeypatch, tmp_path, capsys):
|
||||
home = self._installed(monkeypatch, tmp_path)
|
||||
home.mkdir()
|
||||
(home / ".env").write_text("LLM_PROVIDER=openai\n")
|
||||
monkeypatch.chdir(home)
|
||||
cli._announce_home()
|
||||
assert "is not used" not in capsys.readouterr().err
|
||||
|
||||
|
||||
class TestApi:
|
||||
def test_gunicorn_runs_with_the_image_settings_and_leaves_argv_alone(self, monkeypatch, capsys):
|
||||
application = MagicMock()
|
||||
|
||||
Reference in new issue
Block a user