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
5 changes: 5 additions & 0 deletions doc/changes/unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,14 @@

## Summary

## Bug

* #692: Fixed bug where creating first release failed due to no previous tags

## Documentation

* #585: Added instructions how to ignore sonar issues to the User Guide
* #630: Updated cookiecutter command to reduce errors experienced by users

## Refactoring

Expand Down
6 changes: 5 additions & 1 deletion doc/user_guide/getting_started.rst
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,18 @@ Use the following command to create a new project:
.. code-block:: shell

cookiecutter https://github.com/exasol/python-toolbox.git \
--checkout <latest-tag> --directory project-template
--checkout <latest-tag> --directory project-template \
--overwrite-if-exists

.. note::

Without option :code:`--checkout` cookiecutter will use the main branch of the PTB. In order
to get reliable and reproducible results, we recommend using the tag of PTB's latest released
version instead.

Without option :code:`--overwrite-if-exists`, the cookiecutter command will fail if
you already have files in that directory, like from ``git init``.

**2. Follow the interactive project setup prompt**

**3. Bootstrap the development environment**
Expand Down
16 changes: 15 additions & 1 deletion exasol/toolbox/util/dependencies/shared_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
from contextlib import contextmanager
from dataclasses import dataclass
from pathlib import Path
from subprocess import (
CalledProcessError, # nosec: B404 - risk of using subprocess is acceptable
)
from typing import (
Annotated,
Final,
Expand All @@ -25,6 +28,13 @@
VERSION_TYPE = Annotated[str, AfterValidator(lambda v: Version(v))]


class LatestTagNotFoundError(Exception):
"""Raised when the requested latest tag cannot be found in the repository."""

def __init__(self, *args):
super().__init__("The latest git tag was not found in the repository.", *args)


def normalize_package_name(package_name: str) -> NormalizedPackageStr:
return NormalizedPackageStr(package_name.lower().replace("_", "-"))

Expand Down Expand Up @@ -64,7 +74,11 @@ def files(self) -> tuple[str, ...]:
@contextmanager
def poetry_files_from_latest_tag(root_path: Path) -> Generator[Path]:
"""Context manager to set up a temporary directory with poetry files from the latest tag"""
latest_tag = Git.get_latest_tag()
try:
latest_tag = Git.get_latest_tag()
except CalledProcessError:
raise LatestTagNotFoundError()

path = root_path.relative_to(Git.toplevel())
with tempfile.TemporaryDirectory() as tmpdir_str:
tmp_dir = Path(tmpdir_str)
Expand Down
14 changes: 11 additions & 3 deletions exasol/toolbox/util/release/changelog.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations

from collections import OrderedDict
from datetime import datetime
from inspect import cleandoc
from pathlib import Path
Expand All @@ -8,6 +9,7 @@
get_dependencies,
get_dependencies_from_latest_tag,
)
from exasol.toolbox.util.dependencies.shared_models import LatestTagNotFoundError
from exasol.toolbox.util.dependencies.track_changes import DependencyChanges
from exasol.toolbox.util.version import Version

Expand Down Expand Up @@ -77,9 +79,15 @@ def _describe_dependency_changes(self) -> str:
Describe the dependency changes between the latest tag and the current version
for use in the versioned changes file.
"""
previous_dependencies_in_groups = get_dependencies_from_latest_tag(
root_path=self.root_path
)
try:
previous_dependencies_in_groups = get_dependencies_from_latest_tag(
root_path=self.root_path
)
except LatestTagNotFoundError:
# In new projects, there is not a pre-existing tag, and all dependencies
# are considered new.
previous_dependencies_in_groups = OrderedDict()

current_dependencies_in_groups = get_dependencies(
working_directory=self.root_path
)
Expand Down
7 changes: 6 additions & 1 deletion test/integration/project-template/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,14 @@ def new_project(cwd):
project_name = "project"
repo_name = "repo"
package_name = "package"
project_path = cwd / repo_name

subprocess.run(["mkdir", "-p", project_path])
subprocess.run(["git", "init"], cwd=project_path)

subprocess.run(
["cookiecutter", PROJECT_CONFIG.root_path / "project-template", "-o", cwd, "--no-input",
["cookiecutter", PROJECT_CONFIG.root_path / "project-template",
"-o", cwd, "--no-input", "--overwrite-if-exists",
f"project_name={project_name}", f"repo_name={repo_name}",
f"package_name={package_name}",
], capture_output=True, check=True)
Expand Down
6 changes: 6 additions & 0 deletions test/integration/project-template/nox_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,9 @@ def test_package_check(self, poetry_path, run_command):
output = run_command(package_check)

assert output.returncode == 0

def test_release_prepare(self, poetry_path, run_command):
release_prepare = self._command(poetry_path, task="release:prepare", add_ons=["--type", "minor", "--no-pr", "--no-branch", "--no-add"])
output = run_command(release_prepare)

assert output.returncode == 0
35 changes: 28 additions & 7 deletions test/unit/util/dependencies/shared_models_test.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
from subprocess import CalledProcessError
from unittest import mock

import pytest
from packaging.version import Version
from pydantic import BaseModel
from pydantic_core._pydantic_core import ValidationError

from exasol.toolbox.util.dependencies.shared_models import (
VERSION_TYPE,
LatestTagNotFoundError,
Package,
PoetryFiles,
poetry_files_from_latest_tag,
Expand Down Expand Up @@ -56,11 +60,28 @@ def test_coordinates():
assert dep.coordinates == "numpy:0.1.0"


def test_poetry_files_from_latest_tag():
latest_tag = Git.get_latest_tag()
with poetry_files_from_latest_tag(root_path=PROJECT_CONFIG.root_path) as tmp_dir:
for file in PoetryFiles().files:
assert (tmp_dir / file).is_file()
class TestPoetryFilesFromLatestTag:
@staticmethod
def test_works_as_expected():
latest_tag = Git.get_latest_tag()
with poetry_files_from_latest_tag(
root_path=PROJECT_CONFIG.root_path
) as tmp_dir:
for file in PoetryFiles().files:
assert (tmp_dir / file).is_file()

contents = (tmp_dir / PoetryFiles.pyproject_toml).read_text()
assert f'version = "{latest_tag}"' in contents
contents = (tmp_dir / PoetryFiles.pyproject_toml).read_text()
assert f'version = "{latest_tag}"' in contents

@staticmethod
def test_raises_exception_when_latest_tag_not_found():
with pytest.raises(LatestTagNotFoundError):
with mock.patch.object(
Git,
"get_latest_tag",
side_effect=CalledProcessError(
cmd="Mocked subprocess error", returncode=1
),
):
with poetry_files_from_latest_tag(root_path=PROJECT_CONFIG.root_path):
pass
27 changes: 27 additions & 0 deletions test/unit/util/release/changelog_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import pytest

from exasol.toolbox.util.dependencies.shared_models import LatestTagNotFoundError
from exasol.toolbox.util.release.changelog import (
UNRELEASED_INITIAL_CONTENT,
Changelogs,
Expand Down Expand Up @@ -84,6 +85,18 @@ def mock_dependencies(dependencies, previous_dependencies):
yield


@pytest.fixture(scope="function")
def mock_new_dependencies(dependencies):
mock_latest_tag_not_found_error = mock.Mock(side_effect=LatestTagNotFoundError)

with mock.patch.multiple(
"exasol.toolbox.util.release.changelog",
get_dependencies_from_latest_tag=mock_latest_tag_not_found_error,
get_dependencies=lambda working_directory: dependencies,
):
yield


@pytest.fixture(scope="function")
def mock_no_dependencies():
with mock.patch.multiple(
Expand Down Expand Up @@ -145,6 +158,20 @@ def test_describe_dependency_changes(changelogs, mock_dependencies):
"* Added dependency `package2:0.2.0`\n"
)

@staticmethod
def test_describe_dependency_changes_without_latest_version(
changelogs, mock_new_dependencies
):
result = changelogs._describe_dependency_changes()
assert result == (
"\n"
"### `main`\n"
"* Added dependency `package1:0.1.0`\n"
"\n"
"### `dev`\n"
"* Added dependency `package2:0.2.0`\n"
)

@staticmethod
@pytest.mark.parametrize(
"groups,expected",
Expand Down