mirror of
https://github.com/copier-org/copier.git
synced 2025-05-05 15:32:54 +00:00
feat(settings): add user settings support with defaults
values (fix #235)
This commit is contained in:
parent
57439e5c18
commit
0a9644d249
@ -139,3 +139,7 @@ class DirtyLocalWarning(UserWarning, CopierWarning):
|
|||||||
|
|
||||||
class ShallowCloneWarning(UserWarning, CopierWarning):
|
class ShallowCloneWarning(UserWarning, CopierWarning):
|
||||||
"""The template repository is a shallow clone."""
|
"""The template repository is a shallow clone."""
|
||||||
|
|
||||||
|
|
||||||
|
class MissingSettingsWarning(UserWarning, CopierWarning):
|
||||||
|
"""Settings path has been defined but file is missing."""
|
||||||
|
@ -46,6 +46,7 @@ from .errors import (
|
|||||||
YieldTagInFileError,
|
YieldTagInFileError,
|
||||||
)
|
)
|
||||||
from .jinja_ext import YieldEnvironment, YieldExtension
|
from .jinja_ext import YieldEnvironment, YieldExtension
|
||||||
|
from .settings import Settings
|
||||||
from .subproject import Subproject
|
from .subproject import Subproject
|
||||||
from .template import Task, Template
|
from .template import Task, Template
|
||||||
from .tools import (
|
from .tools import (
|
||||||
@ -58,13 +59,7 @@ from .tools import (
|
|||||||
scantree,
|
scantree,
|
||||||
set_git_alternates,
|
set_git_alternates,
|
||||||
)
|
)
|
||||||
from .types import (
|
from .types import MISSING, AnyByStrDict, JSONSerializable, RelativePath, StrOrPath
|
||||||
MISSING,
|
|
||||||
AnyByStrDict,
|
|
||||||
JSONSerializable,
|
|
||||||
RelativePath,
|
|
||||||
StrOrPath,
|
|
||||||
)
|
|
||||||
from .user_data import DEFAULT_DATA, AnswersMap, Question
|
from .user_data import DEFAULT_DATA, AnswersMap, Question
|
||||||
from .vcs import get_git
|
from .vcs import get_git
|
||||||
|
|
||||||
@ -192,6 +187,7 @@ class Worker:
|
|||||||
answers_file: RelativePath | None = None
|
answers_file: RelativePath | None = None
|
||||||
vcs_ref: str | None = None
|
vcs_ref: str | None = None
|
||||||
data: AnyByStrDict = field(default_factory=dict)
|
data: AnyByStrDict = field(default_factory=dict)
|
||||||
|
settings: Settings = field(default_factory=Settings.from_file)
|
||||||
exclude: Sequence[str] = ()
|
exclude: Sequence[str] = ()
|
||||||
use_prereleases: bool = False
|
use_prereleases: bool = False
|
||||||
skip_if_exists: Sequence[str] = ()
|
skip_if_exists: Sequence[str] = ()
|
||||||
@ -467,6 +463,7 @@ class Worker:
|
|||||||
question = Question(
|
question = Question(
|
||||||
answers=result,
|
answers=result,
|
||||||
jinja_env=self.jinja_env,
|
jinja_env=self.jinja_env,
|
||||||
|
settings=self.settings,
|
||||||
var_name=var_name,
|
var_name=var_name,
|
||||||
**details,
|
**details,
|
||||||
)
|
)
|
||||||
@ -998,11 +995,14 @@ class Worker:
|
|||||||
)
|
)
|
||||||
subproject_subdir = self.subproject.local_abspath.relative_to(subproject_top)
|
subproject_subdir = self.subproject.local_abspath.relative_to(subproject_top)
|
||||||
|
|
||||||
with TemporaryDirectory(
|
with (
|
||||||
prefix=f"{__name__}.old_copy.",
|
TemporaryDirectory(
|
||||||
) as old_copy, TemporaryDirectory(
|
prefix=f"{__name__}.old_copy.",
|
||||||
prefix=f"{__name__}.new_copy.",
|
) as old_copy,
|
||||||
) as new_copy:
|
TemporaryDirectory(
|
||||||
|
prefix=f"{__name__}.new_copy.",
|
||||||
|
) as new_copy,
|
||||||
|
):
|
||||||
# Copy old template into a temporary destination
|
# Copy old template into a temporary destination
|
||||||
with replace(
|
with replace(
|
||||||
self,
|
self,
|
||||||
|
40
copier/settings.py
Normal file
40
copier/settings.py
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
"""User settings models and helper functions."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
|
import warnings
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
import yaml
|
||||||
|
from platformdirs import user_config_path
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
|
from .errors import MissingSettingsWarning
|
||||||
|
|
||||||
|
ENV_VAR = "COPIER_SETTINGS_PATH"
|
||||||
|
|
||||||
|
|
||||||
|
class Settings(BaseModel):
|
||||||
|
"""User settings model."""
|
||||||
|
|
||||||
|
defaults: dict[str, Any] = Field(default_factory=dict)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_file(cls, settings_path: Path | None = None) -> Settings:
|
||||||
|
"""Load settings from a file."""
|
||||||
|
env_path = os.getenv(ENV_VAR)
|
||||||
|
if settings_path is None:
|
||||||
|
if env_path:
|
||||||
|
settings_path = Path(env_path)
|
||||||
|
else:
|
||||||
|
settings_path = user_config_path("copier") / "settings.yml"
|
||||||
|
if settings_path.is_file():
|
||||||
|
data = yaml.safe_load(settings_path.read_text())
|
||||||
|
return cls.model_validate(data)
|
||||||
|
elif env_path:
|
||||||
|
warnings.warn(
|
||||||
|
f"Settings file not found at {env_path}", MissingSettingsWarning
|
||||||
|
)
|
||||||
|
return cls()
|
@ -23,6 +23,8 @@ from pydantic_core.core_schema import ValidationInfo
|
|||||||
from pygments.lexers.data import JsonLexer, YamlLexer
|
from pygments.lexers.data import JsonLexer, YamlLexer
|
||||||
from questionary.prompts.common import Choice
|
from questionary.prompts.common import Choice
|
||||||
|
|
||||||
|
from copier.settings import Settings
|
||||||
|
|
||||||
from .errors import InvalidTypeError, UserMessageError
|
from .errors import InvalidTypeError, UserMessageError
|
||||||
from .tools import cast_to_bool, cast_to_str, force_str_end
|
from .tools import cast_to_bool, cast_to_str, force_str_end
|
||||||
from .types import MISSING, AnyByStrDict, MissingType, OptStrOrPath, StrOrPath
|
from .types import MISSING, AnyByStrDict, MissingType, OptStrOrPath, StrOrPath
|
||||||
@ -178,6 +180,7 @@ class Question:
|
|||||||
var_name: str
|
var_name: str
|
||||||
answers: AnswersMap
|
answers: AnswersMap
|
||||||
jinja_env: SandboxedEnvironment
|
jinja_env: SandboxedEnvironment
|
||||||
|
settings: Settings = field(default_factory=Settings)
|
||||||
choices: Sequence[Any] | dict[Any, Any] | str = field(default_factory=list)
|
choices: Sequence[Any] | dict[Any, Any] | str = field(default_factory=list)
|
||||||
multiselect: bool = False
|
multiselect: bool = False
|
||||||
default: Any = MISSING
|
default: Any = MISSING
|
||||||
@ -246,7 +249,9 @@ class Question:
|
|||||||
except KeyError:
|
except KeyError:
|
||||||
if self.default is MISSING:
|
if self.default is MISSING:
|
||||||
return MISSING
|
return MISSING
|
||||||
result = self.render_value(self.default)
|
result = self.render_value(
|
||||||
|
self.settings.defaults.get(self.var_name, self.default)
|
||||||
|
)
|
||||||
result = self.cast_answer(result)
|
result = self.cast_answer(result)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
1
docs/reference/settings.md
Normal file
1
docs/reference/settings.md
Normal file
@ -0,0 +1 @@
|
|||||||
|
::: copier.settings
|
39
docs/settings.md
Normal file
39
docs/settings.md
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
# Settings
|
||||||
|
|
||||||
|
Copier settings are stored in `<CONFIG_ROOT>/settings.yml` where `<CONFIG_ROOT>` is the
|
||||||
|
standard configuration directory for your platform:
|
||||||
|
|
||||||
|
- `$XDG_CONFIG_HOME/copier` (`~/.config/copier ` in most cases) on Linux as defined by
|
||||||
|
[XDG Base Directory Specifications](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html)
|
||||||
|
- `~/Library/Application Support/copier` on macOS as defined by
|
||||||
|
[Apple File System Basics](https://developer.apple.com/library/archive/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/FileSystemOverview/FileSystemOverview.html)
|
||||||
|
- `%USERPROFILE%\AppData\Local\copier` on Windows as defined in
|
||||||
|
[Known folders](https://docs.microsoft.com/en-us/windows/win32/shell/known-folders)
|
||||||
|
|
||||||
|
This location can be overridden by setting the `COPIER_SETTINGS_PATH` environment
|
||||||
|
variable.
|
||||||
|
|
||||||
|
## User defaults
|
||||||
|
|
||||||
|
Users may define some reusable default variables in the `defaults` section of the
|
||||||
|
configuration file.
|
||||||
|
|
||||||
|
```yaml title="<CONFIG_ROOT>/settings.yml"
|
||||||
|
defaults:
|
||||||
|
user_name: "John Doe"
|
||||||
|
user_email: john.doe@acme.com
|
||||||
|
```
|
||||||
|
|
||||||
|
This user data will replace the default value of fields of the same name.
|
||||||
|
|
||||||
|
### Well-known variables
|
||||||
|
|
||||||
|
To ensure templates efficiently reuse user-defined variables, we invite template authors
|
||||||
|
to use the following well-known variables:
|
||||||
|
|
||||||
|
| Variable name | Type | Description |
|
||||||
|
| ------------- | ----- | ---------------------- |
|
||||||
|
| `user_name` | `str` | User's full name |
|
||||||
|
| `user_email` | `str` | User's email address |
|
||||||
|
| `github_user` | `str` | User's GitHub username |
|
||||||
|
| `gitlab_user` | `str` | User's GitLab username |
|
@ -11,11 +11,13 @@ nav:
|
|||||||
- Configuring a template: "configuring.md"
|
- Configuring a template: "configuring.md"
|
||||||
- Generating a project: "generating.md"
|
- Generating a project: "generating.md"
|
||||||
- Updating a project: "updating.md"
|
- Updating a project: "updating.md"
|
||||||
|
- Settings: "settings.md"
|
||||||
- Reference:
|
- Reference:
|
||||||
- cli.py: "reference/cli.md"
|
- cli.py: "reference/cli.md"
|
||||||
- errors.py: "reference/errors.md"
|
- errors.py: "reference/errors.md"
|
||||||
- jinja_ext.py: "reference/jinja_ext.md"
|
- jinja_ext.py: "reference/jinja_ext.md"
|
||||||
- main.py: "reference/main.md"
|
- main.py: "reference/main.md"
|
||||||
|
- settings.py: "reference/settings.md"
|
||||||
- subproject.py: "reference/subproject.md"
|
- subproject.py: "reference/subproject.md"
|
||||||
- template.py: "reference/template.md"
|
- template.py: "reference/template.md"
|
||||||
- tools.py: "reference/tools.md"
|
- tools.py: "reference/tools.md"
|
||||||
|
2
poetry.lock
generated
2
poetry.lock
generated
@ -1755,4 +1755,4 @@ type = ["pytest-mypy"]
|
|||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.0"
|
lock-version = "2.0"
|
||||||
python-versions = ">=3.9"
|
python-versions = ">=3.9"
|
||||||
content-hash = "888edb00b45adbcefa12e5e242365976d18e2f7356d522b873e973debbb59517"
|
content-hash = "3c9bb33fa0a43bd04125dbd879cef233ac4e3582431b3e06817f77bc9da84920"
|
||||||
|
@ -41,6 +41,7 @@ pygments = ">=2.7.1"
|
|||||||
pyyaml = ">=5.3.1"
|
pyyaml = ">=5.3.1"
|
||||||
questionary = ">=1.8.1"
|
questionary = ">=1.8.1"
|
||||||
eval-type-backport = { version = ">=0.1.3,<0.3.0", python = "<3.10" }
|
eval-type-backport = { version = ">=0.1.3,<0.3.0", python = "<3.10" }
|
||||||
|
platformdirs = ">=4.3.6"
|
||||||
|
|
||||||
[tool.poetry.group.dev]
|
[tool.poetry.group.dev]
|
||||||
optional = true
|
optional = true
|
||||||
|
@ -2,7 +2,9 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import platform
|
import platform
|
||||||
import sys
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
from typing import Any, Iterator
|
from typing import Any, Iterator
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from coverage.tracer import CTracer
|
from coverage.tracer import CTracer
|
||||||
@ -75,3 +77,18 @@ def gitconfig(gitconfig: GitConfig) -> Iterator[GitConfig]:
|
|||||||
"""
|
"""
|
||||||
with local.env(GIT_CONFIG_GLOBAL=str(gitconfig)):
|
with local.env(GIT_CONFIG_GLOBAL=str(gitconfig)):
|
||||||
yield gitconfig
|
yield gitconfig
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def config_path(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Iterator[Path]:
|
||||||
|
config_path = tmp_path / "config"
|
||||||
|
monkeypatch.delenv("COPIER_SETTINGS_PATH", raising=False)
|
||||||
|
with patch("copier.settings.user_config_path", return_value=config_path):
|
||||||
|
yield config_path
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def settings_path(config_path: Path) -> Path:
|
||||||
|
config_path.mkdir()
|
||||||
|
settings_path = config_path / "settings.yml"
|
||||||
|
return settings_path
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from textwrap import dedent
|
from textwrap import dedent
|
||||||
from typing import Any, Callable
|
from typing import Any, Callable
|
||||||
@ -91,6 +92,59 @@ def test_read_data(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_settings_defaults_precedence(
|
||||||
|
tmp_path_factory: pytest.TempPathFactory, settings_path: Path
|
||||||
|
) -> None:
|
||||||
|
src, dst = map(tmp_path_factory.mktemp, ("src", "dst"))
|
||||||
|
build_file_tree(
|
||||||
|
{
|
||||||
|
(src / "copier.yml"): (
|
||||||
|
f"""\
|
||||||
|
# This is a comment
|
||||||
|
_envops: {BRACKET_ENVOPS_JSON}
|
||||||
|
a_string: lorem ipsum
|
||||||
|
a_number: 12345
|
||||||
|
a_boolean: true
|
||||||
|
a_list:
|
||||||
|
- one
|
||||||
|
- two
|
||||||
|
- three
|
||||||
|
"""
|
||||||
|
),
|
||||||
|
(src / "user_data.txt.jinja"): (
|
||||||
|
"""\
|
||||||
|
A string: [[ a_string ]]
|
||||||
|
A number: [[ a_number ]]
|
||||||
|
A boolean: [[ a_boolean ]]
|
||||||
|
A list: [[ ", ".join(a_list) ]]
|
||||||
|
"""
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
settings_path.write_text(
|
||||||
|
dedent(
|
||||||
|
"""\
|
||||||
|
defaults:
|
||||||
|
a_string: whatever
|
||||||
|
a_number: 42
|
||||||
|
a_boolean: false
|
||||||
|
a_list:
|
||||||
|
- one
|
||||||
|
- two
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
)
|
||||||
|
copier.run_copy(str(src), dst, defaults=True, overwrite=True)
|
||||||
|
assert (dst / "user_data.txt").read_text() == dedent(
|
||||||
|
"""\
|
||||||
|
A string: whatever
|
||||||
|
A number: 42
|
||||||
|
A boolean: False
|
||||||
|
A list: one, two
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_invalid_yaml(capsys: pytest.CaptureFixture[str]) -> None:
|
def test_invalid_yaml(capsys: pytest.CaptureFixture[str]) -> None:
|
||||||
conf_path = Path("tests", "demo_invalid", "copier.yml")
|
conf_path = Path("tests", "demo_invalid", "copier.yml")
|
||||||
with pytest.raises(InvalidConfigFileError):
|
with pytest.raises(InvalidConfigFileError):
|
||||||
@ -445,6 +499,7 @@ def test_user_defaults(
|
|||||||
"user_defaults_updated",
|
"user_defaults_updated",
|
||||||
"data_initial",
|
"data_initial",
|
||||||
"data_updated",
|
"data_updated",
|
||||||
|
"settings_defaults",
|
||||||
"expected_initial",
|
"expected_initial",
|
||||||
"expected_updated",
|
"expected_updated",
|
||||||
),
|
),
|
||||||
@ -460,6 +515,53 @@ def test_user_defaults(
|
|||||||
},
|
},
|
||||||
{},
|
{},
|
||||||
{},
|
{},
|
||||||
|
{},
|
||||||
|
dedent(
|
||||||
|
"""\
|
||||||
|
A string: foo
|
||||||
|
"""
|
||||||
|
),
|
||||||
|
dedent(
|
||||||
|
"""\
|
||||||
|
A string: foo
|
||||||
|
"""
|
||||||
|
),
|
||||||
|
),
|
||||||
|
# Settings defaults takes precedence over initial defaults.
|
||||||
|
# The output should remain unchanged following the update operation.
|
||||||
|
(
|
||||||
|
{},
|
||||||
|
{},
|
||||||
|
{},
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
"a_string": "bar",
|
||||||
|
},
|
||||||
|
dedent(
|
||||||
|
"""\
|
||||||
|
A string: bar
|
||||||
|
"""
|
||||||
|
),
|
||||||
|
dedent(
|
||||||
|
"""\
|
||||||
|
A string: bar
|
||||||
|
"""
|
||||||
|
),
|
||||||
|
),
|
||||||
|
# User provided defaults takes precedence over initial defaults and settings defaults.
|
||||||
|
# The output should remain unchanged following the update operation.
|
||||||
|
(
|
||||||
|
{
|
||||||
|
"a_string": "foo",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"a_string": "foobar",
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
"a_string": "bar",
|
||||||
|
},
|
||||||
dedent(
|
dedent(
|
||||||
"""\
|
"""\
|
||||||
A string: foo
|
A string: foo
|
||||||
@ -484,6 +586,9 @@ def test_user_defaults(
|
|||||||
"a_string": "yosemite",
|
"a_string": "yosemite",
|
||||||
},
|
},
|
||||||
{},
|
{},
|
||||||
|
{
|
||||||
|
"a_string": "bar",
|
||||||
|
},
|
||||||
dedent(
|
dedent(
|
||||||
"""\
|
"""\
|
||||||
A string: yosemite
|
A string: yosemite
|
||||||
@ -510,6 +615,9 @@ def test_user_defaults(
|
|||||||
{
|
{
|
||||||
"a_string": "red rocks",
|
"a_string": "red rocks",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"a_string": "bar",
|
||||||
|
},
|
||||||
dedent(
|
dedent(
|
||||||
"""\
|
"""\
|
||||||
A string: yosemite
|
A string: yosemite
|
||||||
@ -529,8 +637,10 @@ def test_user_defaults_updated(
|
|||||||
user_defaults_updated: AnyByStrDict,
|
user_defaults_updated: AnyByStrDict,
|
||||||
data_initial: AnyByStrDict,
|
data_initial: AnyByStrDict,
|
||||||
data_updated: AnyByStrDict,
|
data_updated: AnyByStrDict,
|
||||||
|
settings_defaults: AnyByStrDict,
|
||||||
expected_initial: str,
|
expected_initial: str,
|
||||||
expected_updated: str,
|
expected_updated: str,
|
||||||
|
settings_path: Path,
|
||||||
) -> None:
|
) -> None:
|
||||||
src, dst = map(tmp_path_factory.mktemp, ("src", "dst"))
|
src, dst = map(tmp_path_factory.mktemp, ("src", "dst"))
|
||||||
with local.cwd(src):
|
with local.cwd(src):
|
||||||
@ -557,6 +667,7 @@ def test_user_defaults_updated(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
git_init()
|
git_init()
|
||||||
|
settings_path.write_text(f"defaults: {json.dumps(settings_defaults)}")
|
||||||
|
|
||||||
copier.run_copy(
|
copier.run_copy(
|
||||||
str(src),
|
str(src),
|
||||||
|
71
tests/test_settings.py
Normal file
71
tests/test_settings.py
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from copier.errors import MissingSettingsWarning
|
||||||
|
from copier.settings import Settings
|
||||||
|
|
||||||
|
|
||||||
|
def test_default_settings() -> None:
|
||||||
|
settings = Settings()
|
||||||
|
|
||||||
|
assert settings.defaults == {}
|
||||||
|
|
||||||
|
|
||||||
|
def test_settings_from_default_location(settings_path: Path) -> None:
|
||||||
|
settings_path.write_text("defaults:\n foo: bar")
|
||||||
|
|
||||||
|
settings = Settings.from_file()
|
||||||
|
|
||||||
|
assert settings.defaults == {"foo": "bar"}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures("config_path")
|
||||||
|
def test_settings_from_default_location_dont_exists() -> None:
|
||||||
|
settings = Settings.from_file()
|
||||||
|
|
||||||
|
assert settings.defaults == {}
|
||||||
|
|
||||||
|
|
||||||
|
def test_settings_from_env_location(
|
||||||
|
settings_path: Path, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
||||||
|
) -> None:
|
||||||
|
settings_path.write_text("defaults:\n foo: bar")
|
||||||
|
|
||||||
|
settings_from_env_path = tmp_path / "settings.yml"
|
||||||
|
settings_from_env_path.write_text("defaults:\n foo: baz")
|
||||||
|
|
||||||
|
monkeypatch.setenv("COPIER_SETTINGS_PATH", str(settings_from_env_path))
|
||||||
|
|
||||||
|
settings = Settings.from_file()
|
||||||
|
|
||||||
|
assert settings.defaults == {"foo": "baz"}
|
||||||
|
|
||||||
|
|
||||||
|
def test_settings_from_param(
|
||||||
|
settings_path: Path, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
||||||
|
) -> None:
|
||||||
|
settings_path.write_text("defaults:\n foo: bar")
|
||||||
|
|
||||||
|
settings_from_env_path = tmp_path / "settings.yml"
|
||||||
|
settings_from_env_path.write_text("defaults:\n foo: baz")
|
||||||
|
|
||||||
|
monkeypatch.setenv("COPIER_SETTINGS_PATH", str(settings_from_env_path))
|
||||||
|
|
||||||
|
file_path = tmp_path / "file.yml"
|
||||||
|
file_path.write_text("defaults:\n from: file")
|
||||||
|
|
||||||
|
settings = Settings.from_file(file_path)
|
||||||
|
|
||||||
|
assert settings.defaults == {"from": "file"}
|
||||||
|
|
||||||
|
|
||||||
|
def test_settings_defined_but_missing(
|
||||||
|
settings_path: Path, monkeypatch: pytest.MonkeyPatch
|
||||||
|
) -> None:
|
||||||
|
monkeypatch.setenv("COPIER_SETTINGS_PATH", str(settings_path))
|
||||||
|
|
||||||
|
with pytest.warns(MissingSettingsWarning):
|
||||||
|
Settings.from_file()
|
Loading…
x
Reference in New Issue
Block a user