Skip to content

Commit

Permalink
Added layer_name to RenderedLayer
Browse files Browse the repository at this point in the history
This allows for detection of multiple rendered directories, and proper writing of the composition file.
coordt committed Jun 5, 2022
1 parent c1246d0 commit 7f02db3
Showing 5 changed files with 58 additions and 83 deletions.
38 changes: 26 additions & 12 deletions cookie_composer/composition.py
Original file line number Diff line number Diff line change
@@ -2,10 +2,11 @@
from typing import Any, Dict, List, Optional, Union

import logging
import os
from enum import Enum
from pathlib import Path

from pydantic import AnyHttpUrl, BaseModel, DirectoryPath, Field
from pydantic import AnyHttpUrl, BaseModel, DirectoryPath, Field, root_validator

from cookie_composer.exceptions import MissingCompositionFileError
from cookie_composer.matching import rel_fnmatch
@@ -89,9 +90,7 @@ class LayerConfig(BaseModel):
overwrite_exclude: List[str] = Field(default_factory=list)
"""Paths or glob patterns to exclude from overwriting."""

merge_strategies: Dict[str, MergeStrategy] = Field(
default_factory=lambda: {"*": "do-not-merge"}
)
merge_strategies: Dict[str, MergeStrategy] = Field(default_factory=lambda: {"*": "do-not-merge"})
"""The method to merge specific paths or glob patterns."""


@@ -110,6 +109,25 @@ class RenderedLayer(BaseModel):
latest_commit: Optional[str] = None
"""The latest commit checkout out."""

layer_name: Optional[str] = None
"""The name of the rendered template directory."""

@root_validator(pre=True)
def set_layer_name(cls, values):
"""Set the ``layer_name`` to the name of the rendered template directory."""
if "layer_name" in values:
return values

dirs = list(os.scandir(values["location"]))
if len(dirs) > 1:
raise ValueError("More than one item in render location.")
elif len(dirs) == 0:
raise ValueError("There are no items in render location.")
if not dirs[0].is_dir():
raise ValueError("The rendered template is not a directory.")
values["layer_name"] = dirs[0].name
return values


class ProjectComposition(BaseModel):
"""Composition of templates for a project."""
@@ -131,9 +149,7 @@ def is_composition_file(path_or_url: Union[str, Path]) -> bool:
return Path(path_or_url).suffix in {".yaml", ".yml"}


def read_composition(
path_or_url: Union[str, Path], destination: Union[str, Path]
) -> ProjectComposition:
def read_composition(path_or_url: Union[str, Path], destination: Union[str, Path]) -> ProjectComposition:
"""
Read a JSON or YAML file and return a ProjectComposition.
@@ -148,17 +164,15 @@ def read_composition(
MissingCompositionFileError: Raised when it can not access the configuration file.
"""
import fsspec
from ruyaml import YAML
from ruamel.yaml import YAML

yaml = YAML(typ="safe")
try:
of = fsspec.open(path_or_url, mode="rt")
with of as f:
contents = list(yaml.load_all(f))
templates = [LayerConfig(**doc) for doc in contents]
return ProjectComposition(
layers=templates, destination=Path(destination).expanduser().resolve()
)
return ProjectComposition(layers=templates, destination=Path(destination).expanduser().resolve())
except (ValueError, FileNotFoundError) as e:
raise MissingCompositionFileError(path_or_url) from e

@@ -172,7 +186,7 @@ def write_composition(layers: list, destination: Union[str, Path]):
destination: Where to write the file
"""
import fsspec
from ruyaml import YAML
from ruamel.yaml import YAML

yaml = YAML(typ="safe")
of = fsspec.open(destination, mode="wt")
22 changes: 13 additions & 9 deletions cookie_composer/layers.py
Original file line number Diff line number Diff line change
@@ -73,7 +73,6 @@ def render_layer(layer_config: LayerConfig, render_dir: Path, full_context: Mapp
if full_context and "_copy_without_render" in full_context:
del full_context["_copy_without_render"]

print(config_dict["default_context"])
context = generate_context(
context_file=Path(layer_config.template) / "cookiecutter.json",
default_context=config_dict["default_context"],
@@ -84,13 +83,6 @@ def render_layer(layer_config: LayerConfig, render_dir: Path, full_context: Mapp
# TODO: Get the latest commit, if it is a git repository
latest_commit = None

rendered_layer = RenderedLayer(
layer=layer_config,
location=render_dir,
new_context=context["cookiecutter"],
latest_commit=latest_commit,
)

# call cookiecutter's generate files function
generate_files(
repo_dir=repo_dir,
@@ -99,6 +91,13 @@ def render_layer(layer_config: LayerConfig, render_dir: Path, full_context: Mapp
output_dir=str(render_dir),
)

rendered_layer = RenderedLayer(
layer=layer_config,
location=render_dir,
new_context=context["cookiecutter"],
latest_commit=latest_commit,
)

if cleanup:
rmtree(repo_dir)

@@ -192,9 +191,14 @@ def process_composition(composition: ProjectComposition):
full_context = comprehensive_merge(full_context, rendered_layer.new_context)

layers = []
layer_names = set()
for rendered_layer in rendered_layers:
rendered_layer.layer.commit = rendered_layer.latest_commit
rendered_layer.layer.context = rendered_layer.new_context
layers.append(rendered_layer.layer)
layer_names.add(rendered_layer.layer_name)

composition_file = composition.destination / ".composition.yaml"
if len(layer_names) != 1:
raise ValueError("Can not write composition file because multiple directories were rendered.")
composition_file = composition.destination / layer_names.pop() / ".composition.yaml"
write_composition(layers, composition_file)
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{%- for pkg, version in cookiecutter._requirements.items() %}
{%- endfor %}
{{ pkg }}{{ version }}
{%- endfor %}
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{%- for pkg, version in cookiecutter._requirements.items() %}
{%- endfor %}
{{ pkg }}{{ version }}
{%- endfor %}
77 changes: 17 additions & 60 deletions tests/test_layers.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
"""Test layer rendering."""
import json
import os
import shutil
from pathlib import Path

@@ -13,9 +12,7 @@ def test_render_layer(fixtures_path, tmp_path):
"""Test rendering a layer."""
layer_conf = LayerConfig(template=str(fixtures_path / "template1"), no_input=True)
rendered_layer = layers.render_layer(layer_conf, tmp_path)
expected_context = json.loads(
Path(fixtures_path / "template1/cookiecutter.json").read_text()
)
expected_context = json.loads(Path(fixtures_path / "template1/cookiecutter.json").read_text())
expected_context["repo_name"] = "fake-project-template"
expected = RenderedLayer(
layer=layer_conf,
@@ -33,14 +30,9 @@ def test_get_write_strategy_skip_generation(fixtures_path):
skip_generation=["README.md"],
skip_if_file_exists=False,
)
rendered_layer = RenderedLayer(
layer=layer_config, location=fixtures_path, new_context={}
)
rendered_layer = RenderedLayer(layer=layer_config, location=fixtures_path, new_context={}, layer_name="test")
filepath = fixtures_path / "template1" / "{{cookiecutter.repo_name}}" / "README.md"
assert (
layers.get_write_strategy(filepath, filepath, rendered_layer)
== layers.WriteStrategy.SKIP
)
assert layers.get_write_strategy(filepath, filepath, rendered_layer) == layers.WriteStrategy.SKIP


def test_get_write_strategy_dest_not_exist(tmp_path, fixtures_path):
@@ -50,15 +42,10 @@ def test_get_write_strategy_dest_not_exist(tmp_path, fixtures_path):
template=str(fixtures_path / "template1"),
skip_if_file_exists=True,
)
rendered_layer = RenderedLayer(
layer=layer_config, location=fixtures_path, new_context={}
)
rendered_layer = RenderedLayer(layer=layer_config, location=fixtures_path, new_context={}, layer_name="test")
filepath = fixtures_path / "template1" / "{{cookiecutter.repo_name}}" / "README.md"
dest_path = tmp_path / "foo" / "README.md"
assert (
layers.get_write_strategy(filepath, dest_path, rendered_layer)
== layers.WriteStrategy.WRITE
)
assert layers.get_write_strategy(filepath, dest_path, rendered_layer) == layers.WriteStrategy.WRITE


def test_get_write_strategy_merge_strategy(fixtures_path):
@@ -73,19 +60,11 @@ def test_get_write_strategy_merge_strategy(fixtures_path):
},
skip_if_file_exists=True,
)
rendered_layer = RenderedLayer(
layer=layer_config, location=fixtures_path, new_context={}
)
rendered_layer = RenderedLayer(layer=layer_config, location=fixtures_path, new_context={}, layer_name="test")
filepath = fixtures_path / "existing.yaml"
assert (
layers.get_write_strategy(filepath, filepath, rendered_layer)
== layers.WriteStrategy.MERGE
)
assert layers.get_write_strategy(filepath, filepath, rendered_layer) == layers.WriteStrategy.MERGE
filepath = fixtures_path / "existing.json"
assert (
layers.get_write_strategy(filepath, filepath, rendered_layer)
== layers.WriteStrategy.SKIP
)
assert layers.get_write_strategy(filepath, filepath, rendered_layer) == layers.WriteStrategy.SKIP


def test_get_write_strategy_overwrite_exclude(fixtures_path):
@@ -96,14 +75,9 @@ def test_get_write_strategy_overwrite_exclude(fixtures_path):
overwrite_exclude=["*.yaml"],
skip_if_file_exists=True,
)
rendered_layer = RenderedLayer(
layer=layer_config, location=fixtures_path, new_context={}
)
rendered_layer = RenderedLayer(layer=layer_config, location=fixtures_path, new_context={}, layer_name="test")
filepath = fixtures_path / "existing.yaml"
assert (
layers.get_write_strategy(filepath, filepath, rendered_layer)
== layers.WriteStrategy.SKIP
)
assert layers.get_write_strategy(filepath, filepath, rendered_layer) == layers.WriteStrategy.SKIP


def test_get_write_strategy_overwrite(fixtures_path):
@@ -114,14 +88,9 @@ def test_get_write_strategy_overwrite(fixtures_path):
overwrite=["*.yaml"],
skip_if_file_exists=True,
)
rendered_layer = RenderedLayer(
layer=layer_config, location=fixtures_path, new_context={}
)
rendered_layer = RenderedLayer(layer=layer_config, location=fixtures_path, new_context={}, layer_name="test")
filepath = fixtures_path / "existing.yaml"
assert (
layers.get_write_strategy(filepath, filepath, rendered_layer)
== layers.WriteStrategy.WRITE
)
assert layers.get_write_strategy(filepath, filepath, rendered_layer) == layers.WriteStrategy.WRITE


def test_get_write_strategy_skip_if_file_exists(fixtures_path):
@@ -131,20 +100,12 @@ def test_get_write_strategy_skip_if_file_exists(fixtures_path):
template=str(fixtures_path / "template1"),
skip_if_file_exists=True,
)
rendered_layer = RenderedLayer(
layer=layer_config, location=fixtures_path, new_context={}
)
rendered_layer = RenderedLayer(layer=layer_config, location=fixtures_path, new_context={}, layer_name="test")
filepath = fixtures_path / "existing.yaml"
assert (
layers.get_write_strategy(filepath, filepath, rendered_layer)
== layers.WriteStrategy.SKIP
)
assert layers.get_write_strategy(filepath, filepath, rendered_layer) == layers.WriteStrategy.SKIP

rendered_layer.layer.skip_if_file_exists = False
assert (
layers.get_write_strategy(filepath, filepath, rendered_layer)
== layers.WriteStrategy.WRITE
)
assert layers.get_write_strategy(filepath, filepath, rendered_layer) == layers.WriteStrategy.WRITE


def test_merge_layers(tmp_path, fixtures_path):
@@ -166,9 +127,7 @@ def test_merge_layers(tmp_path, fixtures_path):
context2 = json.loads((fixtures_path / "rendered2" / "context.json").read_text())
full_context = comprehensive_merge(context1, context2)
rendered2_config = RenderedLayer(
layer=layer_config,
location=fixtures_path / "rendered2",
new_context=full_context,
layer=layer_config, location=fixtures_path / "rendered2", new_context=full_context, layer_name="testproject"
)
# merge the layers
layers.merge_layers(rendered_layer_path, rendered2_config)
@@ -180,7 +139,5 @@ def test_merge_layers(tmp_path, fixtures_path):
about_content = (rendered_layer_path / "testproject/ABOUT.md").read_text()
assert about_content == "# Intentionally different name\n"

requirements_content = (
rendered_layer_path / "testproject/requirements.txt"
).read_text()
requirements_content = (rendered_layer_path / "testproject/requirements.txt").read_text()
assert requirements_content == "bar>=5.0.0\nbaz\nfoo\n"

0 comments on commit 7f02db3

Please sign in to comment.