Skip to content

Commit

Permalink
feat(config): allow overriding config from environment
Browse files Browse the repository at this point in the history
  • Loading branch information
noirbizarre committed Dec 19, 2023
1 parent 45a5eb7 commit 9edc5c8
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 6 deletions.
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,17 @@ dependency-mapping = {"package-name"= {"repo"= "https://github.com/example/packa
> Note: the `dependency-mapping` is merged with the default mapping, so you don't need to specify the default mapping if you want to add a new mapping.
> Repos urls will be normalized to http(s), with the trailing slash removed.
### From environment

Some settings are overridable by environment variables with the following `SYNC_PRE_COMMIT_LOCK_*` prefixed environement variables:

| `toml` setting | environment | format |
| -----------------------------|----------------------------------------|-----------------------------------|
| `automaticall-install-hooks` | `SYNC_PRE_COMMIT_LOCK_INSTALL` | `bool` as string (`true`, `1`...) |
| `disable-sync-from-lock` | `SYNC_PRE_COMMIT_LOCK_DISABLED` | `bool` as string (`true`, `1`...) |
| `ignore` | `SYNC_PRE_COMMIT_LOCK_IGNORE` | coma-seprated list |
| `pre-commit-config-file` | `SYNC_PRE_COMMIT_LOCK_PRE_COMMIT_FILE` | `str` |

## Usage

Once installed, and optionally configured, the plugin usage should be transparent, and trigger when you run applicable PDM or Poetry commands, like `pdm lock`, or `poetry lock`.
Expand Down
42 changes: 37 additions & 5 deletions src/sync_pre_commit_lock/config.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations

import os
from dataclasses import dataclass, field
from pathlib import Path
from typing import TYPE_CHECKING, Any
Expand All @@ -17,6 +18,22 @@
pass


def env_as_bool(value: str) -> bool:
return (value or "False").lower() in ("true", "1")


def env_as_list(value: str) -> list:
return [v.strip() for v in (value or "").split(",")]


ENV_PREFIX = "SYNC_PRE_COMMIT_LOCK"
ENV_CAST = {
"DISABLED": env_as_bool,
"IGNORE": env_as_list,
"INSTALL": env_as_bool,
}


def from_toml(data: dict[str, Any]) -> SyncPreCommitLockConfig:
fields = {f.metadata.get("toml", f.name): f for f in SyncPreCommitLockConfig.__dataclass_fields__.values()}
# XXX We should warn about unknown fields
Expand All @@ -33,12 +50,27 @@ def from_toml(data: dict[str, Any]) -> SyncPreCommitLockConfig:
)


def update_from_env(config: SyncPreCommitLockConfig) -> SyncPreCommitLockConfig:
vars = {
f.metadata["env"]: f for f in SyncPreCommitLockConfig.__dataclass_fields__.values() if f.metadata.get("env")
}
for var, specs in vars.items():
if value := os.getenv(f"{ENV_PREFIX}_{var}"):
caster = ENV_CAST.get(var, lambda v: v)
setattr(config, specs.name, caster(value))
return config


@dataclass
class SyncPreCommitLockConfig:
automatically_install_hooks: bool = field(default=True, metadata={"toml": "automatically-install-hooks"})
disable_sync_from_lock: bool = field(default=False, metadata={"toml": "disable-sync-from-lock"})
ignore: list[str] = field(default_factory=list, metadata={"toml": "ignore"})
pre_commit_config_file: str = field(metadata={"toml": "pre-commit-config-file"}, default=".pre-commit-config.yaml")
automatically_install_hooks: bool = field(
default=True, metadata={"toml": "automatically-install-hooks", "env": "INSTALL"}
)
disable_sync_from_lock: bool = field(default=False, metadata={"toml": "disable-sync-from-lock", "env": "DISABLED"})
ignore: list[str] = field(default_factory=list, metadata={"toml": "ignore", "env": "IGNORE"})
pre_commit_config_file: str = field(
metadata={"toml": "pre-commit-config-file", "env": "PRE_COMMIT_FILE"}, default=".pre-commit-config.yaml"
)
dependency_mapping: PackageRepoMapping = field(default_factory=dict, metadata={"toml": "dependency-mapping"})


Expand All @@ -52,4 +84,4 @@ def load_config(path: Path | None = None) -> SyncPreCommitLockConfig:
if not tool_dict or len(tool_dict) == 0:
return SyncPreCommitLockConfig()

return from_toml(tool_dict)
return update_from_env(from_toml(tool_dict))
38 changes: 37 additions & 1 deletion tests/test_config.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from unittest.mock import MagicMock, patch

from sync_pre_commit_lock.config import SyncPreCommitLockConfig, from_toml, load_config
import pytest
from sync_pre_commit_lock.config import SyncPreCommitLockConfig, from_toml, load_config, update_from_env
from sync_pre_commit_lock.db import RepoInfo


Expand All @@ -23,6 +24,24 @@ def test_from_toml() -> None:
assert actual_config == expected_config


def test_update_from_env(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setenv("SYNC_PRE_COMMIT_LOCK_DISABLED", "1")
monkeypatch.setenv("SYNC_PRE_COMMIT_LOCK_INSTALL", "false")
monkeypatch.setenv("SYNC_PRE_COMMIT_LOCK_IGNORE", "a, b")
monkeypatch.setenv("SYNC_PRE_COMMIT_LOCK_PRE_COMMIT_FILE", ".test-config.yaml")
expected_config = SyncPreCommitLockConfig(
automatically_install_hooks=False,
disable_sync_from_lock=True,
ignore=["a", "b"],
pre_commit_config_file=".test-config.yaml",
dependency_mapping={},
)

actual_config = update_from_env(SyncPreCommitLockConfig())

assert actual_config == expected_config


def test_sync_pre_commit_lock_config() -> None:
config = SyncPreCommitLockConfig(
disable_sync_from_lock=True,
Expand Down Expand Up @@ -63,3 +82,20 @@ def test_load_config_with_data(mock_from_toml: MagicMock, mock_open: MagicMock,
mock_path.open.assert_called_once_with("rb")
mock_load.assert_called_once()
mock_from_toml.assert_called_once_with({"disable": True})


@patch("sync_pre_commit_lock.config.toml.load", return_value={"tool": {"sync-pre-commit-lock": {"ignore": ["fake"]}}})
@patch("builtins.open", new_callable=MagicMock)
def test_env_override_config(mock_open: MagicMock, mock_load: MagicMock, monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setenv("SYNC_PRE_COMMIT_LOCK_DISABLED", "true")
monkeypatch.setenv("SYNC_PRE_COMMIT_LOCK_IGNORE", "a, b")
expected_config = SyncPreCommitLockConfig(
disable_sync_from_lock=True,
ignore=["a", "b"],
)
mock_path = MagicMock()
mock_path.open = mock_open(read_data="dummy_stream")
actual_config = load_config(mock_path)

assert actual_config == expected_config
mock_path.open.assert_called_once_with("rb")

0 comments on commit 9edc5c8

Please sign in to comment.