Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 2 additions & 18 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
54 changes: 54 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
@@ -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="<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/)
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
4 changes: 2 additions & 2 deletions src/github_rest_cli/api.py
Original file line number Diff line number Diff line change
@@ -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


Expand Down Expand Up @@ -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}"

Expand Down
15 changes: 8 additions & 7 deletions src/github_rest_cli/config.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
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,
messages={
"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.
7 changes: 5 additions & 2 deletions src/github_rest_cli/globals.py
Original file line number Diff line number Diff line change
@@ -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)
Expand Down
43 changes: 43 additions & 0 deletions tests/test_config.py
Original file line number Diff line number Diff line change
@@ -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)
8 changes: 4 additions & 4 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading