diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e207f40..2fb43dc 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -64,25 +64,9 @@ just profile --help just profile list-repo --format json ``` -## Configuration (Dynaconf) +## Configuration -The app uses [Dynaconf](https://www.dynaconf.com/) with `envvar_prefix="GITHUB"`. Settings can come from environment variables or from `settings.toml` / `.secrets.toml` (see `src/github_rest_cli/config.py`). - -List defined parameters: - -```shell -just dl -# equivalent: just dynaconf-list -``` - -Validate parameters: - -```shell -just dv -# equivalent: just dynaconf-validate -``` - -**Note:** Dynaconf validation expects `dynaconf_validators.toml` to exist at the project root. +See [docs/configuration.md](docs/configuration.md) for environment variables, optional settings files, and Dynaconf tooling (`just dl` / `just dv`). ## Lint and format diff --git a/README.md b/README.md index 7a679d0..5540acd 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ Suggested classic PAT scopes: Fine-grained tokens need repository access with permissions for Contents, Administration (create/delete), Environments, and Dependabot/security alerts as needed. -The CLI reads configuration via Dynaconf using the `GITHUB_` environment variable prefix (`GITHUB_AUTH_TOKEN` maps to `AUTH_TOKEN`). +For optional API URL overrides, settings files, and environments, see [Configuration](docs/configuration.md). ## Quick start diff --git a/docs/configuration.md b/docs/configuration.md new file mode 100644 index 0000000..af416d0 --- /dev/null +++ b/docs/configuration.md @@ -0,0 +1,54 @@ +# Configuration + +`github-rest-cli` uses Dynaconf for settings. Prefer environment variables; optional files are supported for local development. + +## Environment variables (recommended) + +| Variable | Setting | Required | Description | +| --- | --- | --- | --- | +| `GITHUB_AUTH_TOKEN` | `AUTH_TOKEN` | Yes | GitHub personal access token | +| `GITHUB_API_URL` | `API_URL` | No | GitHub REST API base URL (default: `https://api.github.com`) | +| `SET_ENV` | environment switcher | No | Active Dynaconf environment (`development`, `testing`, `production`, …) | + +Example: + +```shell +export GITHUB_AUTH_TOKEN="" +# optional: +export GITHUB_API_URL="https://api.github.com" +``` + +## Optional settings files + +When present in the **current working directory**, Dynaconf loads: + +1. `settings.toml` +2. `.secrets.toml` (gitignored; for local secrets) + +File defaults for local clones live in the repository `settings.toml` (including `API_URL`). An installed package does not ship these files; env vars are enough. + +## Contributor tooling + +List defined parameters: + +```shell +just dl +# equivalent: just dynaconf-list +``` + +Validate parameters: + +```shell +just dv +# equivalent: just dynaconf-validate +``` + +Validation expects `dynaconf_validators.toml` at the project root. + +Implementation lives in `src/github_rest_cli/config.py`. + +## References + +- [dynaconf/dynaconf](https://github.com/dynaconf/dynaconf) +- [Dynaconf documentation](https://www.dynaconf.com/) +- [Dynaconf API](https://www.dynaconf.com/api/) diff --git a/pyproject.toml b/pyproject.toml index e6608cb..13049b1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ authors = [ dependencies = [ "requests>=2.31.0", "rich>=14.0.0", - "dynaconf>=3.2.11", + "dynaconf>=3.3.2", "prettytable>=3.16.0", ] readme = "README.md" diff --git a/src/github_rest_cli/api.py b/src/github_rest_cli/api.py index db94617..aabf8a7 100644 --- a/src/github_rest_cli/api.py +++ b/src/github_rest_cli/api.py @@ -1,5 +1,5 @@ import requests -from github_rest_cli.globals import GITHUB_URL, get_headers +from github_rest_cli.globals import get_api_url, get_headers from github_rest_cli.utils import rich_output, CliOutput @@ -35,7 +35,7 @@ def build_url(*segments: str) -> str: Result: https://api.github.com/repos/org/repo/environments/prod """ - base = GITHUB_URL.rstrip("/") + base = get_api_url() path = "/".join(segment.strip("/") for segment in segments) return f"{base}/{path}" diff --git a/src/github_rest_cli/config.py b/src/github_rest_cli/config.py index 77d0b6d..854acf5 100644 --- a/src/github_rest_cli/config.py +++ b/src/github_rest_cli/config.py @@ -1,14 +1,18 @@ from dynaconf import Dynaconf, Validator +DEFAULT_API_URL = "https://api.github.com" + settings = Dynaconf( envvar_prefix="GITHUB", - settings_files=["../../settings.toml", "../../.secrets.toml"], - environments=["development", "testing", "production"], + settings_files=["settings.toml", ".secrets.toml"], + environments=True, env_switcher="SET_ENV", + validators=[ + Validator("API_URL", default=DEFAULT_API_URL), + ], ) -# The CLI will not work if the variable -# defined in the Validator class are not defined. +# The CLI will not work if AUTH_TOKEN is not set (GITHUB_AUTH_TOKEN). AUTH_TOKEN_VALIDATOR = Validator( "AUTH_TOKEN", must_exist=True, @@ -16,6 +20,3 @@ "must_exist_true": "Environment variable GITHUB_AUTH_TOKEN is not set. Please set it and try again." }, ) - -# `envvar_prefix` = export envvars with `export DYNACONF_FOO=bar`. -# `settings_files` = Load these files in the order. diff --git a/src/github_rest_cli/globals.py b/src/github_rest_cli/globals.py index 798ba79..30cfba7 100644 --- a/src/github_rest_cli/globals.py +++ b/src/github_rest_cli/globals.py @@ -1,12 +1,15 @@ -from github_rest_cli.config import settings, AUTH_TOKEN_VALIDATOR +from github_rest_cli.config import settings, AUTH_TOKEN_VALIDATOR, DEFAULT_API_URL from dynaconf.base import ValidationError import logging -GITHUB_URL = "https://api.github.com" logger = logging.getLogger(__name__) +def get_api_url() -> str: + return settings.get("API_URL", DEFAULT_API_URL).rstrip("/") + + def get_headers(): try: AUTH_TOKEN_VALIDATOR.validate(settings) diff --git a/tests/test_config.py b/tests/test_config.py new file mode 100644 index 0000000..e7e4c7f --- /dev/null +++ b/tests/test_config.py @@ -0,0 +1,43 @@ +import inspect + +from github_rest_cli import config as config_module +from github_rest_cli.config import settings, DEFAULT_API_URL +from github_rest_cli.globals import get_api_url +from github_rest_cli import api + + +def test_settings_files_are_basenames(): + source = inspect.getsource(config_module) + assert 'settings_files=["settings.toml", ".secrets.toml"]' in source + assert "../" not in source + + +def test_get_api_url_default(): + url = get_api_url() + assert url == DEFAULT_API_URL.rstrip("/") + assert not url.endswith("/") + + +def test_build_url_uses_default_api_url(): + assert api.build_url("user") == f"{DEFAULT_API_URL}/user" + assert api.build_url("repos", "owner", "repo") == ( + f"{DEFAULT_API_URL}/repos/owner/repo" + ) + + +def test_build_url_uses_custom_api_url(mocker): + mocker.patch( + "github_rest_cli.api.get_api_url", + return_value="https://github.example.com/api/v3", + ) + + assert api.build_url("user") == "https://github.example.com/api/v3/user" + + +def test_get_api_url_from_settings(): + original = settings.get("API_URL", DEFAULT_API_URL) + try: + settings.set("API_URL", "https://github.example.com/api/v3/") + assert get_api_url() == "https://github.example.com/api/v3" + finally: + settings.set("API_URL", original) diff --git a/uv.lock b/uv.lock index 4d81366..e0e8a1e 100644 --- a/uv.lock +++ b/uv.lock @@ -75,11 +75,11 @@ wheels = [ [[package]] name = "dynaconf" -version = "3.2.13" +version = "3.3.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/57/0e/05927cf459e73f8bf9a9277cbea6f2d5b7db8a5cc9dc1e20e7a5fbac1b90/dynaconf-3.2.13.tar.gz", hash = "sha256:d79e0189d97b3f226b8ebb1717e2ce05d1a05cdf6ea05de66d24625fdb5a0cbd", size = 283507, upload-time = "2026-03-17T19:38:47.632Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2e/fa/351d165f6f9fe493a92a2e155f3097a4379dbe23e731b68543ce9988ee19/dynaconf-3.3.2.tar.gz", hash = "sha256:3b50232b774142702c3d4623633bcd76bb9951abf8567b7f1340d73a30a80899", size = 327805, upload-time = "2026-06-29T20:55:41.706Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/97/43/11d6e5d2c00bf000b5329717c74563bf76a9193f4a41cb0c4ef277dde4fa/dynaconf-3.2.13-py2.py3-none-any.whl", hash = "sha256:4305527aef4834bdba3e39479b23c005186e83fb85f65bcaa4bcea58fa26759b", size = 238041, upload-time = "2026-03-17T19:38:45.337Z" }, + { url = "https://files.pythonhosted.org/packages/bb/7d/1e50069bb7d9774acbcb7d9509a4be95348e6476009f083d46cca67c5dbc/dynaconf-3.3.2-py3-none-any.whl", hash = "sha256:4bb8ac4222af0bb4315e3da40022c283ed140bafb80c36282a7973404a67cd8a", size = 270081, upload-time = "2026-06-29T20:55:40.066Z" }, ] [[package]] @@ -102,7 +102,7 @@ dev = [ [package.metadata] requires-dist = [ - { name = "dynaconf", specifier = ">=3.2.11" }, + { name = "dynaconf", specifier = ">=3.3.2" }, { name = "prettytable", specifier = ">=3.16.0" }, { name = "requests", specifier = ">=2.31.0" }, { name = "rich", specifier = ">=14.0.0" },