Skip to content

Commit

Permalink
Use strong typing for config
Browse files Browse the repository at this point in the history
  • Loading branch information
mtkennerly committed Nov 19, 2023
1 parent 57463f4 commit be0ddf2
Show file tree
Hide file tree
Showing 5 changed files with 119 additions and 26 deletions.
1 change: 1 addition & 0 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"recommendations": [
"codezombiech.gitignore",
"EditorConfig.EditorConfig",
"ms-python.mypy-type-checker",
"ms-python.python",
"redhat.vscode-yaml",
"sidneys1.gitconfig",
Expand Down
6 changes: 1 addition & 5 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
{
"python.formatting.provider": "black",
"python.linting.enabled": true,
"python.linting.flake8Enabled": false,
"python.linting.mypyEnabled": true,
"python.linting.pylintEnabled": false,
"mypy-type-checker.importStrategy": "fromEnvironment",
"python.defaultInterpreterPath": "${workspaceFolder}/.venv",
"yaml.format.enable": true,
}
133 changes: 113 additions & 20 deletions poetry_dynamic_versioning/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,19 +30,91 @@
_BYPASS_ENV = "POETRY_DYNAMIC_VERSIONING_BYPASS"
_OVERRIDE_ENV = "POETRY_DYNAMIC_VERSIONING_OVERRIDE"

if sys.version_info >= (3, 8):
from typing import TypedDict

_SubstitutionPattern = TypedDict(
"_SubstitutionPattern",
{
"value": str,
"mode": Optional[str],
},
)

_SubstitutionFolder = TypedDict(
"_SubstitutionFolder",
{
"path": str,
"files": Optional[Sequence[str]],
"patterns": Optional[Sequence[Union[str, _SubstitutionPattern]]],
},
)

_Substitution = TypedDict(
"_Substitution",
{
"files": Sequence[str],
"patterns": Sequence[Union[str, _SubstitutionPattern]],
"folders": Sequence[_SubstitutionFolder],
},
)

_File = TypedDict(
"_File", {"persistent-substitution": Optional[bool], "initial-content": Optional[str]}
)

_JinjaImport = TypedDict(
"_JinjaImport",
{
"module": str,
"item": Optional[str],
},
)

_Config = TypedDict(
"_Config",
{
"enable": bool,
"vcs": str,
"dirty": bool,
"pattern": Optional[str],
"latest-tag": bool,
"substitution": _Substitution,
"files": Mapping[str, _File],
"style": Optional[str],
"metadata": Optional[bool],
"format": Optional[str],
"format-jinja": Optional[str],
"format-jinja-imports": Sequence[_JinjaImport],
"bump": bool,
"tagged-metadata": bool,
"full-commit": bool,
"tag-branch": Optional[str],
"tag-dir": str,
"strict": bool,
"fix-shallow-repository": bool,
},
)
else:

class _Config(Mapping):
pass


class _ProjectState:
def __init__(
self,
path: Path,
original_version: str,
version: str,
substitutions: MutableMapping[Path, str] = None,
substitutions: Optional[MutableMapping[Path, str]] = None,
) -> None:
self.path = path
self.original_version = original_version
self.version = version
self.substitutions = {} if substitutions is None else substitutions
self.substitutions = (
{} if substitutions is None else substitutions
) # type: MutableMapping[Path, str]


class _State:
Expand Down Expand Up @@ -80,16 +152,16 @@ def __init__(self, path: Path, files: Sequence[str], patterns: Sequence[_SubPatt
self.patterns = patterns

@staticmethod
def from_config(config: Mapping, root: Path) -> Sequence["_FolderConfig"]:
def from_config(config: _Config, root: Path) -> Sequence["_FolderConfig"]:
files = config["substitution"]["files"]
patterns = _SubPattern.from_config(config["substitution"]["patterns"])

main = _FolderConfig(root, files, patterns)
extra = [
_FolderConfig(
root / x["path"],
x.get("files", files),
_SubPattern.from_config(x["patterns"]) if "patterns" in x else patterns,
x["files"] if x["files"] is not None else files,
_SubPattern.from_config(x["patterns"]) if x["patterns"] is not None else patterns,
)
for x in config["substitution"]["folders"]
]
Expand Down Expand Up @@ -145,7 +217,7 @@ def _deep_merge_dicts(base: Mapping, addition: Mapping) -> Mapping:
return result


def _find_higher_file(*names: str, start: Path = None) -> Optional[Path]:
def _find_higher_file(*names: str, start: Optional[Path] = None) -> Optional[Path]:
# Note: We need to make sure we get a pathlib object. Many tox poetry
# helpers will pass us a string and not a pathlib object. See issue #40.
if start is None:
Expand All @@ -159,7 +231,7 @@ def _find_higher_file(*names: str, start: Path = None) -> Optional[Path]:
return None


def _get_pyproject_path(start: Path = None) -> Optional[Path]:
def _get_pyproject_path(start: Optional[Path] = None) -> Optional[Path]:
return _find_higher_file("pyproject.toml", start=start)


Expand All @@ -177,11 +249,31 @@ def _get_pyproject_path_from_poetry(pyproject) -> Path:
raise RuntimeError("Unable to determine pyproject.toml path from Poetry instance")


def _get_config(local: Mapping) -> Mapping:
return _deep_merge_dicts(_default_config(), local)["tool"]["poetry-dynamic-versioning"]
def _get_config(local: Mapping) -> _Config:
def initialize(data, key):
if isinstance(data, dict) and key not in data:
data[key] = None

merged = _deep_merge_dicts(_default_config(), local)["tool"][
"poetry-dynamic-versioning"
] # type: _Config

def _get_config_from_path(start: Path = None) -> Mapping:
# Add default values so we don't have to worry about missing keys
for x in merged["files"].values():
initialize(x, "initial-content")
initialize(x, "persistent-substitution")
for x in merged["format-jinja-imports"]:
initialize(x, "item")
for x in merged["substitution"]["folders"]:
initialize(x, "files")
initialize(x, "patterns")
for x in merged["substitution"]["patterns"]:
initialize(x, "mode")

return merged


def _get_config_from_path(start: Optional[Path] = None) -> Mapping:
pyproject_path = _get_pyproject_path(start)
if pyproject_path is None:
return _default_config()["tool"]["poetry-dynamic-versioning"]
Expand Down Expand Up @@ -272,7 +364,7 @@ def _get_override_version(name: Optional[str], env: Optional[Mapping] = None) ->


def _get_version_from_dunamai(
vcs: Vcs, pattern: Union[str, Pattern], config: Mapping, *, strict: Optional[bool] = None
vcs: Vcs, pattern: Union[str, Pattern], config: _Config, *, strict: Optional[bool] = None
):
return Version.from_vcs(
vcs,
Expand All @@ -285,17 +377,17 @@ def _get_version_from_dunamai(
)


def _get_version(config: Mapping, name: Optional[str] = None) -> str:
def _get_version(config: _Config, name: Optional[str] = None) -> str:
override = _get_override_version(name)
if override is not None:
return override

vcs = Vcs(config["vcs"])
style = config["style"]
if style is not None:
style = Style(style)
style = Style(config["style"]) if config["style"] is not None else None

pattern = config["pattern"] if config["pattern"] is not None else Pattern.Default
pattern = (
config["pattern"] if config["pattern"] is not None else Pattern.Default
) # type: Union[str, Pattern]

if config["fix-shallow-repository"]:
# We start without strict so we can inspect the concerns.
Expand Down Expand Up @@ -339,7 +431,7 @@ def _get_version(config: Mapping, name: Optional[str] = None) -> str:
for entry in config["format-jinja-imports"]:
if "module" in entry:
module = import_module(entry["module"])
if "item" in entry:
if entry["item"] is not None:
custom_context[entry["item"]] = getattr(module, entry["item"])
else:
custom_context[entry["module"]] = module
Expand Down Expand Up @@ -415,7 +507,7 @@ def _substitute_version_in_text(version: str, content: str, patterns: Sequence[_


def _apply_version(
version: str, config: Mapping, pyproject_path: Path, retain: bool = False
version: str, config: _Config, pyproject_path: Path, retain: bool = False
) -> None:
pyproject = tomlkit.parse(pyproject_path.read_text(encoding="utf-8"))

Expand All @@ -433,7 +525,8 @@ def _apply_version(

for file_name, file_info in config["files"].items():
full_file = pyproject_path.parent.joinpath(file_name)
if "initial-content" in file_info:

if file_info["initial-content"] is not None:
if not full_file.parent.exists():
full_file.parent.mkdir()
initial = textwrap.dedent(file_info["initial-content"])
Expand Down Expand Up @@ -504,7 +597,7 @@ def _revert_version(retain: bool = False) -> None:

persistent = []
for file, file_info in config["files"].items():
if file_info.get("persistent-substitution"):
if file_info["persistent-substitution"]:
persistent.append(state.path.parent.joinpath(file))

for file, content in state.substitutions.items():
Expand Down
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ poetry-dynamic-versioning = "poetry_dynamic_versioning.plugin:DynamicVersioningP
[tool.black]
line-length = 100

[tool.mypy]
allow_redefinition = true

[tool.ruff]
line-length = 100
extend-select = ["W605", "N"]
Expand Down
2 changes: 1 addition & 1 deletion tests/test_unit.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ def test__get_version__format_jinja_with_enforced_style(config):

def test__get_version__format_jinja_imports_with_module_only(config):
config["format-jinja"] = "{{ math.pow(2, 2) }}"
config["format-jinja-imports"] = [{"module": "math"}]
config["format-jinja-imports"] = [{"module": "math", "item": None}]
assert plugin._get_version(config) == "4.0"


Expand Down

0 comments on commit be0ddf2

Please sign in to comment.